2010-06-08 13 views
5

Ejemplo A:¿Debo usar una interfaz o fábrica (y una interfaz) para una implementación multiplataforma?

// 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(); 
} 

Ejemplo 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(); 
} 

¿Cuál es la mejor opción, el ejemplo A, B, ninguna o "depende"?

Respuesta

0

El problema con A es que tiene que implementar todos los métodos de IFoo en Foo. Eso no es gran cosa si solo hay un par, pero es doloroso si hay docenas de ellos. Si está trabajando con un lenguaje compatible con métodos de fábrica, tales como Curl, entonces se podría poner un método de fábrica en 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

Interesante respuesta, sin embargo estoy usando C#, pero la fábrica es de hecho todavía una opción. Me pregunto si hay desventajas claras de usar una fábrica en este escenario. –

0

interfaces se utilizan cuando es posible que puedan existir múltiples implementaciones de un solo conjunto funcional . Esto suena como si se aplica a su escenario particular.

En términos de sus ejemplos, definitivamente rodaría con B, es más fácil de mantener. A incorpora demasiada lógica común [es decir, detección de plataforma] dentro de clases individuales [y/o métodos]. Si va a construir su propia clase Factory, intente generalizarla [a través de un método genérico Resolve<IType>() o algo así], a diferencia de un método \ clase por interfaz.

Por ejemplo,

// 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; 
    } 
} 

Sin embargo, en lugar de poner en práctica su propio patrón de fábrica, es posible que desee investigar implementaciones alternativas, tales como la esclerosis múltiple o de Unity2.0 castillo de Windsor de CastleWindsorContainer. Estos son fáciles de configurar y consumir.

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, supongo que debe de gritar a Ninject y StructureMap también. Simplemente no estoy tan familiarizado con estos como con Unity o CastleWindsor.

0

Si me preguntas B es mucho mejor, ya que Foo no necesita hacer ninguna conexión en la plataforma. ¿Por que importa? Bueno, ya que probablemente quiera probar todos los componentes por separado: Foo con una 'prueba' IFoo, FooPlatformA por separado en la plataforma A y FooPlatformB en la plataforma B. Si tiene la opción dentro de Foo, debe probar Foo tanto en A como en B, no solo los diferentes IFoos. Combina los componentes sin ninguna razón aparente.

5

Yo diría que su opción de fábrica explícita (opción B) es generalmente mejor.

En su primer ejemplo, su clase Foo está realizando efectivamente dos trabajos, es una fábrica y es un proxy. Dos trabajos, una clase, me inquieta.

Su segunda opción le da un poco más de responsabilidad al cliente: necesitan saber usar la fábrica, pero este es un modismo muy utilizado que creo que no es difícil de entender.

+0

Excelente respuesta. Para ser sincero, las 4 funciones me llevaron al límite. Volver a implementar mi proxy/envoltura/modelo de fábrica en una buena fábrica limpia en este momento. En una nota relacionada, podría pasar un comentario sobre la clase CArch en sinergia (demuestra algo similar al ejemplo A): http://synergy2.svn.sourceforge.net/viewvc/synergy2/trunk/lib/arch/Arch. cpp? view = marcado –

+0

Gracias. Veo que el ejemplo se centra mucho en la creación de un proxy. Desde el punto de vista del cliente, se utiliza un objeto de arquitectura simple, estamos desperdiciando el esfuerzo de crear esos métodos de proxy para simplificar la vida del cliente. El método de fábrica es trivial. Si empezamos a tener muchas variaciones de la lógica de fábrica, y especialmente si nos beneficiaríamos de un patrón abstracto de fábrica, entonces preferiría refactorizar ese código de fábrica. En este momento, estamos dando mayor peso a la simplicidad del cliente. – djna

0

La fábrica es una solución más limpia ya que no tiene que implementar cada miembro de la interfaz en el contenedor class Foo : IFoo. Imagine, cada vez que modifique la interfaz IFoo que necesitaría actualizar el contenedor. Cuando programe, según sus objetivos, trate de considerar la capacidad de mantenimiento tanto como sea posible.

¿Están todas las 'plataformas' disponibles o solo una de ellas? ¿La única diferencia entre las plataformas es la lógica? Pensando desde la perspectiva de un desarrollador de juegos, usaría #defines para implementar esto.

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

HTH,

+1

También, mientras que en el tema de las mejores prácticas; Entiendo que usar Foo, Bar, FooBar es un paradigma de programación común, pero sinceramente tuve que leer su ejemplo varias veces para comprender su intención. : | – Dennis

+0

Hmm, ya veo. Tendré esto en cuenta en el futuro. –

Cuestiones relacionadas