2014-11-13 10 views
5

Sto lavorando a un progetto API e c'è qualcosa su Java e il polimorfismo a cui non ho pensato fino ad ora. Se creo un'API in questo modo:Java, Polymorphism, Static Typing e il pattern "QueryInterface"

interface FooFactory 
{ 
    public Foo getFoo(); 
} 

interface Foo 
{ 
    public void sayFoo(); 
} 

quindi l'unica cosa che il mio FooFactory implementazione può essere invocata per fornire è un'implementazione Foo. Se decido di fornire alcuni metodi avanzati, in questo modo:

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

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

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

e l'unico modo i miei clienti API possono utilizzare l'interfaccia EnhancedFoo è se ottengono un'interfaccia Foo e cercare di lanciarlo come il EnhancedFoo.

Mi ricordo il modo in cui Microsoft ha gestito COM nell'interfaccia IUnknown:

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

L'idea è che si passa in un GUID per l'interfaccia che si desidera, in caso di successo, si torna un puntatore che vi garantisce puoi tranquillamente lanciarlo nell'interfaccia che stavi cercando.

avrei potuto fare qualcosa di simile in un modo typesafe con Java:

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

dove ho fornire un'implementazione su richiesta che o restituisce un'istanza della sottointerfaccia desiderato, oppure restituisce null se non è disponibile.

La mia domanda, è:

  • è il modello QueryInterface ragionevole?
  • devo usare la fusione?
  • o è l'unico modo corretto per gestire il polimorfismo nell'API per limitare i client a utilizzare rigorosamente i metodi semplici in questione? (Ad esempio i metodi in Foo nel mio esempio e non le eventuali EnhancedFoo metodi)

Chiarimento: L'implementazione fabbrica non sta per essere conosciuto da codice client. (Supponiamo che utilizzi l'iniezione di dipendenza o qualche architettura di provider di servizi come java's o NetBeans Lookup). Quindi, come cliente, non so cosa sia disponibile. La fabbrica potrebbe avere accesso a diversi derivati ​​ e voglio che il client sia in grado di richiedere un set di funzionalità che desidera, e o lo ottiene, oppure dovrà ricorrere alla funzionalità di base .

Immagino che la parte difficile per me sia che qui c'è una dipendenza in fase di esecuzione ... l'approccio tipicamente statico puro in cui tutto è fissato in fase di compilazione significa che posso solo dipendere da quella funzionalità di base Foo. Questo approccio mi è familiare, ma poi perdo le possibili funzionalità avanzate. Considerando che l'approccio più dinamico/opportunistico è qualcosa che può trarre vantaggio da queste caratteristiche, ma non sono sicuro del modo giusto di progettare un sistema che lo utilizza.

risposta

1

Si potrebbe semplicemente dichiarare la fabbrica con i generici e lasciare il resto come è:

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

Poi grazie a inferenza di tipo, questa sarà la compilazione:

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

(questo potrebbe non funzionare prima Java 8)

Se il FooFactory non è quello che vi aspettavate, la linea EnhancedFoo e = f.getFoo(); sarà gettare un ClassCastException.

+0

L'inferenza di tipo è stata migliorata in Java 8. – assylias

+0

Hmm ... l'approccio di eccezione è normale per Python ma sembra inappropriato per Java. –

+0

Direi che è perfettamente appropriato - meglio di null-checking – Arkadiy

1

Perché non parametrizzare l'interfaccia di fabbrica?

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

poi:

class EnhancedFooFactoryImpl implements FooFactory<EnhancedFoo> { 

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

E esemplificazione:

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

La maggiore fabbrica, naturalmente, può essere usato dove è atteso classe di base.

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

EDIT (in risposta al tuo commento)

Se per il vostro disegno è meglio avere metodo factory parametrizzata non la classe fabbrica, allora è una buona pratica per definire come qui sotto:

interface FooFactory { 

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

Questo vi dà due vantaggi in più: 1. È possibile controllare quale gruppo deve eseguire l'utente. 2. Avendo un oggetto di classe è possibile istanziarlo con la riflessione.

Quindi in questo caso non c'è bisogno di avere a corsi di fabbrica speciali per una maggiore foo:

class FooFactoryImpl implements FooFactory { 

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

Poi utilizzo è come di seguito:

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

Se qualche implementazione Foo richiede parametri del costruttore voi può sempre mettere corrispondente se nel metodo foctory e istanziare l'oggetto "manualmente":

@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

Credo che il mio pensiero sia che ci sono capacità specifiche che un cliente potrebbe desiderare di usare. Potrebbero esserci più di un'estensione disponibile per la classe 'Foo', quindi non è qualcosa che voglio lasciare alla fabbrica; invece dovrebbe essere al cliente. Sono abbastanza inesperto nel design delle API che non sono sicuro al 100% su come formulare la mia domanda con precisione. –

+0

ha aggiunto un chiarimento –

+0

@JasonS Ok, l'ho indirizzato nella mia modifica. –