2010-06-08 7 views
5

Esempio A:Devo usare un'interfaccia o fabbrica (e interfaccia) per un'implementazione multipiattaforma?

// pseudo code 
interface IFoo { 
    void bar(); 
} 

class FooPlatformA : IFoo { 
    void bar() { /* ... */ } 
} 

class FooPlatformB : IFoo { 
    void bar() { /* ... */ } 
} 

class Foo : IFoo { 
    IFoo m_foo; 
    public Foo() { 
     if (detectPlatformA()} { 
      m_foo = new FooPlatformA(); 
     } else { 
      m_foo = new FooPlatformB(); 
     } 
    } 

    // wrapper function - downside is we'd have to create one 
    // of these for each function, which doesn't seem right. 
    void bar() { 
     m_foo.bar(); 
    } 
} 

Main() { 
    Foo foo = new Foo(); 
    foo.bar(); 
} 

Esempio B:

// pseudo code 
interface IFoo { 
    void bar(); 
} 

class FooPlatformA : IFoo { 
    void bar() { /* ... */ } 
} 

class FooPlatformB : IFoo { 
    void bar() { /* ... */ } 
} 

class FooFactory { 
    IFoo newFoo() { 
     if (detectPlatformA()} { 
      return new FooPlatformA(); 
     } else { 
      return new FooPlatformB(); 
     } 
    } 
} 

Main() { 
    FooFactory factory = new FooFactory(); 
    IFoo foo = factory.newFoo(); 
    foo.bar(); 
} 

che è l'opzione migliore, ad esempio A, B, nessuno dei due, o "dipende"?

risposta

0

Il problema con A è che devi implementare tutti i metodi di IFoo in Foo. Questo non è un grosso problema se ci sono solo un paio, ma è un dolore se ce ne sono dozzine. Se si lavora con un linguaggio che supporta i metodi di fabbrica, come Curl, allora si potrebbe mettere un metodo factory in IFoo:

{define-class abstract IFoo 
    {method abstract {bar}:void} 
    {factory {default}:{this-class} 
     {if platformA? then 
      {return {FooPlatformA}} 
     else 
      {return {FooPlatformB}} 
     } 
    } 
} 

{define-class FooPlatformA {inherits IFoo} 
     {method {bar}:void} 
} 

... 

def foo = {IFoo} 
{foo.bar} 
+0

risposta interessante, però sto usando C#, ma in fabbrica è infatti ancora un'opzione. Mi chiedo se ci siano dei chiari svantaggi nell'utilizzare una fabbrica in questo scenario. –

0

interfacce vengono utilizzati quando è possibile che possano esistere molteplici implementazioni di un unico insieme funzionale . Sembra che si applichi al tuo particolare scenario.

In termini di esempi, rotolerei sicuramente con B, è più facile da mantenere. A incorpora troppa logica comune [vale a dire il rilevamento della piattaforma] all'interno di singole classi [e/o metodi]. Se vuoi costruire la tua classe Factory, prova a generalizzarla [attraverso un metodo generico Resolve<IType>() o qualcosa del genere], invece che con un metodo \ class per interfaccia.

Per esempio,

// i called it a "container" because it "contains" implementations 
// or instantiation methods for requested types - but it *is* a 
// factory. 
public class Container 
{ 
    // "resolves" correct implementation for a requested type. 
    public IType Resolve<IType>() 
    { 
     IType typed = default (IType); 
     if (isPlatformA) 
     { 
      // switch or function map on IType for correct 
      // platform A implementation 
     } 
     else if (isPlatformB) 
     { 
      // switch or function map on IType for correct 
      // platform B implementation 
     } 
     else 
     { 
      // throw NotSupportedException 
     } 
     return typed; 
    } 
} 

Tuttavia, invece di implementare il proprio modello di fabbrica, si potrebbe desiderare di indagare implementazioni alternative, come MS di Unity2.0 o Castello di Windsor di CastleWindsorContainer. Questi sono facili da configurare e consumare.

Idealmente,

// use an interface to isolate *your* code from actual 
// implementation, which could change depending on your needs, 
// for instance if you "roll your own" or switch between Unity, 
// Castle Windsor, or some other vendor 
public interface IContainer 
{ 
    IType Resolve<IType>(); 
} 

// custom "roll your own" container, similar to above, 
public class Container : IContainer { } 

// delegates to an instance of a Unity container, 
public class UnityContainer : IContainer { } 

// delegates to an instance of a CastleWindsorContainer, 
public class CastleWindsorContainer : IContainer { } 

Oh, supponiamo Dovrei gridare al Ninject e StructureMap troppo. Non sono così familiare con questi come con Unity o CastleWindsor.

0

Se mi chiedi, B è molto meglio - dato che Foo non ha bisogno di fare alcuna accensione sulla piattaforma. Perché importa? Bene, dal momento che probabilmente vorrete testare tutti i componenti separatamente - Foo con un 'test' IFoo, FooPlatformA separatamente sulla piattaforma A e FooPlatformB sulla piattaforma B. Se si configura la scelta all'interno di Foo è necessario testare Foo sia su A che su B, non solo i diversi IFoos. Rende i componenti più accoppiati senza una ragione apparente.

5

Direi che la tua opzione di fabbrica esplicita (opzione B) è generalmente migliore.

Nel tuo primo esempio la classe Foo sta effettivamente facendo due lavori, è una fabbrica ed è un proxy. Due lavori, una classe, mi fanno sentire a disagio.

La tua seconda opzione attribuisce un po 'più di responsabilità al cliente: devono sapere per usare la fabbrica, ma questo è un idioma così diffuso che penso non sia difficile da capire.

+0

Ottima risposta. Ad essere onesti, 4 funzioni mi hanno fatto pendere oltre il limite. Ri-implementare il mio modello proxy/wrapper/factory in una bella fabbrica pulita mentre parliamo. Su una nota correlata, si può passare un commento sulla classe CArch in sinergia (si dimostra qualcosa di simile all'esempio A): http://synergy2.svn.sourceforge.net/viewvc/synergy2/trunk/lib/arch/CArch. cpp? view = markup –

+0

Grazie. Considero quell'esempio come molto focalizzato sulla creazione di un proxy. Dal punto di vista del cliente viene utilizzato un semplice oggetto di architettura, stiamo spendendo lo sforzo di creare quei metodi proxy per rendere semplice la vita del cliente. Il metodo factory è banale. Dovremmo iniziare ad avere molte varianti della logica di fabbrica, e specialmente se trarremo vantaggio da un modello di Factory astratta, allora preferirei rifattorizzare quel codice di fabbrica. In questo momento, stiamo dando maggior peso alla semplicità del cliente. – djna

0

La fabbrica è una soluzione più pulita in quanto non è necessario implementare ciascun membro dell'interfaccia nel wrapper class Foo : IFoo. Immagina, ogni volta che modifichi l'interfaccia IFoo, dovresti aggiornare il wrapper. Durante la programmazione, in base ai propri obiettivi, provare a considerare la manutenibilità il più possibile.

Tutte le "piattaforme" sono disponibili o solo una di esse? L'unica differenza tra le piattaforme è la logica? Pensando dalla prospettiva di uno sviluppatore di giochi, userei #defines per implementarlo.

class Platform : IPlatform 
{ 
    void Update() 
    { 
#if PLATFORM_A 
     * ... Logic for platform A */ 
#elif PLATFORM_B 
     * ... Logic for platform A */ 
#endif 
    } 
} 

HTH,

+1

Anche se sul tema delle migliori pratiche; Capisco che usare Foo, Bar, FooBar sia un paradigma di programmazione comune, ma onestamente dovevo leggere più volte il tuo esempio per capire la tua intenzione. : | – Dennis

+0

Hmm, vedo. Lo svelerò in futuro. –