2014-11-13 9 views
5

Ich arbeite an einem API-Design, und es gibt etwas über Java und Polymorphismus, über das ich bisher nicht nachgedacht habe. Wenn ich eine API wie folgt zu erstellen:Java, Polymorphismus, statische Typisierung und das "QueryInterface" -Muster

interface FooFactory 
{ 
    public Foo getFoo(); 
} 

interface Foo 
{ 
    public void sayFoo(); 
} 

dann das einzige, was meine FooFactory Implementierung darauf verlassen können zu schaffen ist eine Foo Implementierung. Wenn ich mich entscheide, einige verbesserte Methoden zur Verfügung zu stellen, wie folgt aus:

interface EnhancedFoo extends Foo 
{ 
    public void patHeadAndRubBelly(); 
} 

class EnhancedFooImpl implements EnhancedFoo 
{ 
    ... implementation here ... 
} 

class EnhancedFooFactoryImpl implements FooFactory 
{ 
    @Override public EnhancedFoo getFoo() { return new EnhancedFooImpl(); } 
} 

und der einzige Weg, um meine API-Clients die EnhancedFoo Schnittstelle verwenden können, ist, wenn sie eine Foo Schnittstelle erhalten und versuchen, es als EnhancedFoo zu werfen.

erinnere ich mich an die Art und Weise Microsoft COM in der IUnknown Schnittstelle behandelt:

HRESULT QueryInterface(
    [in] REFIID riid, 
    [out] void **ppvObject 
); 

Die Idee ist, dass Sie in einer GUID übergeben für die Schnittstelle, die Sie wollen, wenn es gelingt, sie wieder einen Zeiger, die Sie garantiert kann es sicher an die von Ihnen gesuchte Schnittstelle übertragen.

ich etwas ähnliches in einer Art und Weise mit typsicher Java tun könnte:

interface FooFactory 
{ 
    public <T extends Foo> T getFoo(Class<T> fooClass); 
} 

, wo ich eine Implementierung auf Anfrage zur Verfügung stellen, dass entweder eine Instanz des gewünschten Subschnittstelle zurückgibt, oder gibt null zurück, wenn keine vorhanden ist.

Meine Frage ist:

  • ist das Query-Interface Muster sinnvoll?
  • sollte ich nur Casting verwenden?
  • oder ist der einzige richtige Weg, um Polymorphie in der API zu behandeln, um Clients zu beschränken, die fraglichen einfachen Methoden streng zu verwenden? (Zum Beispiel die Verfahren in Foo in meinem Beispiel und keine EnhancedFoo Methoden)

Klarstellung: Die Fabrik Implementierung wird nicht von Client-Code bekannt sein würde. (Lassen Sie uns sagen, dass es Abhängigkeitsinjektion oder einige Service-Provider-Architektur wie Java ServiceLoader oder NetBeans Lookup verwendet.) Also als Client weiß ich nicht, was verfügbar ist. Die Fabrik könnte Zugang zu mehreren Foo Derivaten haben und ich möchte, dass der Client in der Lage ist, einen gewünschten Feature-Set anzufordern, und entweder bekommt er das oder muss auf die Basis-Foo-Funktionalität zurückgreifen.

Ich denke, der schwierige Teil für mich ist, dass es hier Laufzeitabhängigkeit gibt ... der reine statische typsichere Ansatz, bei dem alles zur Kompilierzeit festgelegt ist, bedeutet, dass ich nur auf diese grundlegende Foo Funktionalität angewiesen bin. Dieser Ansatz ist mir vertraut, aber dann verliere ich auf mögliche verbesserte Funktionen. Der dynamischere/opportunistische Ansatz ist etwas, das diese Funktionen nutzen kann, aber ich bin mir nicht sicher, wie ich ein System, das es verwendet, richtig entwerfen kann.

Antwort

1

Sie könnten einfach Ihre Fabrik mit Generika erklären und den Rest lassen wie es ist:

static interface FooFactory { 
    public <T extends Foo> T getFoo(); 
} 

Dann Dank Folgerung geben, wird dies kompilieren:

FooFactory f = new EnhancedFooFactoryImpl(); 
EnhancedFoo e = f.getFoo(); 

(dies kann nicht vor Arbeit zu Java 8)

Wenn die FooFactory nicht das ist, was Sie erwartet haben, wird die Zeile EnhancedFoo e = f.getFoo(); eine ClassCastException werfen.

+0

Typinferenz wurde in Java 8 verbessert. – assylias

+0

Hmm ... der Ausnahmeansatz ist normal für Python, scheint aber für Java ungeeignet. –

+0

Ich würde sagen, es ist vollkommen angemessen - besser als Null-Überprüfung – Arkadiy

1

Warum nicht die Factory-Schnittstelle parametrieren?

static interface FooFactory<T extends Foo> { 
    public T getFoo(); 
} 

dann:

class EnhancedFooFactoryImpl implements FooFactory<EnhancedFoo> { 

    @Override 
    public EnhancedFoo getFoo() { return new EnhancedFooImpl(); } 
} 

Und Instanziierung:

FooFactory<EnhancedFoo> f1 = new EnhancedFooFactoryImpl(); 
EnhancedFoo foo = f1.getFoo(); 

Die verbesserte Fabrik natürlich verwendet werden kann, wo die Basisklasse erwartet wird.

FooFactory<?> f2 = new EnhancedFooFactoryImpl(); 
Foo foo = f2.getFoo(); 

EDIT (auf Antwort auf Ihren Kommentar)

Wenn für Ihr Design ist es besser, parametrisiert Factory-Methode haben, nicht die Fabrik Klasse dann ist es eine gute Praxis, sie zu definieren, wie unten:

Dies gibt Ihnen zwei Vorteile: 1. Sie können steuern, welche actuall clas sa Benutzer erstellen möchte. 2. Mit einem Klassenobjekt können Sie es mit Reflektion instanziieren.

Also in diesem Fall müssen Sie nicht für eine verbesserte foo spezielle Fabrikklassen haben:

class FooFactoryImpl implements FooFactory { 

    @Override 
    public <T extends Foo> T getFoo(Class<T> c) { 
     try { 
      return c.newInstance(); 
     } catch (ReflectiveOperationException e) { 
      return null; 
     } 
    } 
} 

Dann Nutzung wie unten:

FooFactory ff = new FooFactoryImpl(); 
EnhancedFoo ef = ff.getFoo(EnhancedFoo.class); 
Foo f = ff.getFoo(Foo.class); 

Wenn einige Foo Implementierung Konstruktorparameter erfordert, dass Sie kann in der foctory-Methode immer entsprechend setzen und das Objekt "manuell" instanziieren:

@Override 
    public <T extends Foo> T getFoo(Class<T> c) { 

     if(SomeParametrizedFoo.class.equals(c)) { 
      SomeParamtrizedFoo spf = new SomeParamtrizedFoo("constr arg"); 
      spf.setParam(16136); 
      return (T) spf; 
     } 

     try { 
      return c.newInstance(); 
     } catch (ReflectiveOperationException e) { 
      return null; 
     } 
    } 
+0

Ich denke, mein Denken ist, dass es bestimmte Fähigkeiten gibt, die ein Client verwenden möchte. Es könnte mehr als eine Erweiterung für die 'Foo'-Klasse geben, also möchte ich die Fabrik nicht verlassen; stattdessen sollte es Sache des Kunden sein. Ich bin unerfahren genug im API-Design, dass ich mir nicht 100% ig sicher bin, wie ich meine Frage genau formulieren kann. –

+0

hinzugefügt eine Klarstellung –

+0

@JasonS Ok, ich adressiert das in meiner Bearbeitung. –