2010-06-08 3 views
5

Exemple A:Devrais-je utiliser une interface ou une usine (et une interface) pour une implémentation multiplateforme?

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

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

Quelle est la meilleure option, par exemple A, B, ni, ou "ça dépend"?

Répondre

0

Le problème avec A est que vous devez mettre en œuvre toutes les méthodes de IFoo en Foo. Ce n'est pas un gros problème s'il n'y a qu'un couple, mais c'est une douleur s'il y en a des douzaines. Si vous travaillez avec une langue qui prend en charge les méthodes d'usine, comme Curl, vous pouvez mettre une méthode de fabrication 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

réponse intéressante, mais je suis en utilisant C#, mais l'usine est en effet toujours une option. Je me demande s'il y a des désavantages évidents d'utiliser une usine dans ce scénario. –

0

interfaces sont utilisées quand il est possible que plusieurs implémentations d'un seul ensemble fonctionnel peut exister . Cela semble être le cas pour votre scénario particulier.

En ce qui concerne vos exemples, je serais certainement rouler avec B, il est plus facile à entretenir. A intègre trop de logique commune [c.-à-d. Détection de plate-forme] dans des classes [et/ou méthodes] individuelles. Si vous devez construire votre propre classe Factory, essayez de le généraliser [par une méthode générique Resolve<IType>() ou quelque chose], par opposition à une méthode \ classe par interface.

Par exemple,

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

Cependant, plutôt que de mettre en œuvre votre propre modèle d'usine, vous voudrez peut-être enquêter sur des implémentations alternatives, telles que MS de Unity2.0 ou le château de Windsor de CastleWindsorContainer. Ceux-ci sont faciles à configurer et à consommer.

Idéalement,

// 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, suppose que je devrais crier à Ninject et StructureMap aussi. Je ne suis pas aussi familier avec ceux-ci qu'avec Unity ou CastleWindsor.

0

Si vous me demandez B est beaucoup mieux - puisque Foo lui-même n'a pas besoin de faire de commutation sur la plate-forme. Pourquoi est-ce important? Eh bien, puisque vous voulez probablement tester tous les composants séparément - Foo avec un « test » IFoo, FooPlatformA séparément sur la plate-forme A et FooPlatformB sur la plateforme B. Si vous vous en tenez le choix à l'intérieur Foo vous devez tester Foo sur A et B, non seulement les différents IFoos. Rend les composants plus couplés sans raison apparente.

5

je dirais que votre option d'usine explicite (option B) est généralement mieux.

Dans votre premier exemple votre classe Foo fait effectivement deux emplois, il est une usine et il est un proxy. Deux emplois, une classe, me met mal à l'aise. Votre deuxième option met un peu plus de responsabilité sur le client: ils doivent savoir utiliser l'usine, mais c'est un idiome si largement utilisé que je pense qu'il n'est pas difficile à comprendre.

+0

Bonne réponse. Pour être honnête, 4 fonctions m'ont renversé. Ré-implémenter mon modèle proxy/wrapper/factory dans une belle usine propre en ce moment même. Sur une note connexe, pourriez-vous passer un commentaire sur la classe CArch en synergie (cela montre quelque chose de similaire à l'exemple A): http://synergy2.svn.sourceforge.net/viewvc/synergy2/trunk/lib/arch/CArch. cpp? view = markup –

+0

Merci. Je vois cet exemple comme étant très axé sur la création d'un proxy. Du point de vue du client, un simple objet d'architecture est utilisé, nous déployons l'effort de créer ces méthodes proxy afin de simplifier la vie du client. La méthode d'usine est triviale. Devrions-nous commencer à avoir de nombreuses variantes de la logique de l'usine, et surtout si nous devrions bénéficier d'un modèle d'usine abstraite alors je préférerais refactoriser ce code d'usine. À l'heure actuelle, nous accordons le plus grand poids à la simplicité du client. – djna

0

L'usine est une solution plus propre car vous n'avez pas implémenté chaque membre de l'interface dans l'emballage class Foo : IFoo. Imaginez, chaque fois que vous modifiez l'interface IFoo, vous devez mettre à jour le wrapper. Lors de la programmation, en fonction de vos objectifs, essayez de considérer la maintenabilité autant que possible.

Toutes les «plateformes» sont-elles disponibles ou seulement une d'entre elles? Est-ce que la seule différence entre les plates-formes est la logique? En pensant du point de vue du développeur de jeux, j'utiliserais #defines pour l'implémenter.

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

HTH,

+1

Aussi sur le sujet des meilleures pratiques; Je comprends que l'utilisation de Foo, Bar, FooBar est un paradigme de programmation commun, mais honnêtement, vous avez dû lire votre exemple plusieurs fois pour comprendre votre intention. : | – Dennis

+0

Hmm, je vois. Je vais garder cela à l'esprit à l'avenir. –

Questions connexes