2010-06-08 5 views
5

Beispiel A:Soll ich eine Schnittstelle oder Factory (und Schnittstelle) für eine plattformübergreifende Implementierung verwenden?

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

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

Welches ist die bessere Option ist, zB A, B, weder oder "es kommt"?

Antwort

0

Das Problem mit A ist, dass Sie jede Methode von IFoo in Foo implementieren müssen. Das ist keine große Sache, wenn es nur ein Paar gibt, aber es ist ein Schmerz, wenn es Dutzende von ihnen gibt. Wenn Sie mit einer Sprache arbeiten, die Factory-Methoden unterstützt, wie Locke, dann könnten Sie eine Factory-Methode in IFoo setzen:

{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

Interessante Antwort, aber C# Ich verwende, aber Fabrik ist in der Tat immer noch eine Option. Ich frage mich, ob es irgendwelche klaren Nachteile gibt, eine Fabrik in diesem Szenario zu verwenden. –

0

Schnittstellen werden verwendet, wenn es möglich ist, dass mehrere Implementierungen eines einzigen Funktionssatz existieren . Dies klingt, als ob es für Ihr spezielles Szenario gilt.

In Bezug auf Ihre Beispiele würde ich definitiv mit B rollen, es ist einfacher zu pflegen. A bettet zu viel gemeinsame Logik [dh Plattformerkennung] innerhalb einzelner Klassen [und/oder Methoden] ein. Wenn Sie Ihre eigene Factory-Klasse erstellen möchten, versuchen Sie, sie zu verallgemeinern [durch eine generische Methode Resolve<IType>() oder etwas], im Gegensatz zu einer Methode \ Klasse pro Schnittstelle.

Zum Beispiel

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

Doch anstatt Ihre eigenen Fabrik Muster implementieren, können Sie möchten alternative Implementierungen untersuchen, wie MS 'Unity2.0 oder Schloss Windsor CastleWindsorContainer. Diese sind einfach zu konfigurieren und zu konsumieren.

Idealerweise

// 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, ich nehme an Ninject und StructureMap auch schreien sollte. Ich kenne sie nicht so gut wie mit Unity oder CastleWindsor.

0

Wenn du mich fragst, ist B viel besser - da Foo selbst keine Platform einschalten muss. Wieso spielt das eine Rolle? Nun, da Sie wahrscheinlich alle Komponenten separat testen wollen - Foo mit einem 'Test' IFoo, FooPlatformA getrennt auf Plattform A und FooPlatformB auf Plattform B. Wenn Sie die Wahl innerhalb von Foo halten, müssen Sie Foo sowohl auf A als auch auf B testen nur die verschiedenen IFoos. Macht die Komponenten ohne ersichtlichen Grund mehr gekoppelt.

5

Ich würde sagen, dass Ihre explizite Fabrikoption (Option B) im Allgemeinen besser ist.

In Ihrem ersten Beispiel macht Ihre Foo-Klasse effektiv zwei Jobs, es ist eine Fabrik und es ist ein Proxy. Zwei Jobs, eine Klasse, machen mich unruhig.

Ihre zweite Option bedeutet etwas mehr Verantwortung für den Kunden: Sie müssen wissen, um die Fabrik zu benutzen, aber das ist eine so weit verbreitete Redewendung, dass ich denke, dass es nicht schwer zu verstehen ist.

+0

Große Antwort. Um ehrlich zu sein, haben mich 4 Funktionen über den Rand gekippt. Re-Implementation meiner Proxy/Wrapper/Factory-Modell in eine schöne, saubere Fabrik, wie wir sprechen. Bei einer diesbezüglichen Anmerkung könnten Sie bitte einen Kommentar zur CArch-Klasse in synergy übergeben (sie zeigt etwas Ähnliches wie Beispiel A): http://synergy2.svn.sourceforge.net/viewvc/synergy2/trunk/lib/arch/CArch. cpp? view = Markup –

+0

Danke. Ich betrachte dieses Beispiel als sehr konzentriert auf die Erstellung eines Proxy. Aus der Sicht des Clients wird ein einfaches Architekturobjekt verwendet, wir bemühen uns, diese Proxy-Methoden zu erstellen, um das Leben des Kunden zu vereinfachen. Die Fabrikmethode ist trivial. Sollten wir anfangen, viele Variationen der Fabriklogik zu haben, und insbesondere, wenn wir von einem Abstrakt-Fabrik-Muster profitieren würden, würde ich es vorziehen, diesen Fabrikcode zu refaktorieren. Im Moment geben wir der Einfachheit des Kunden das größte Gewicht. – djna

0

Die Fabrik ist eine sauberere Lösung, da Sie nicht jedes Mitglied der Schnittstelle im Wrapper class Foo : IFoo implementieren müssen. Stellen Sie sich vor, jedes Mal, wenn Sie die IFoo-Schnittstelle ändern, müssten Sie den Wrapper aktualisieren. Versuchen Sie bei der Programmierung, abhängig von Ihren Zielen, die Wartbarkeit so gut wie möglich zu berücksichtigen.

Sind alle "Plattformen" verfügbar oder nur eine? Ist der einzige Unterschied zwischen den Plattformen Logik? Aus der Sicht eines Spieleentwicklers würde ich #defines verwenden, um dies zu implementieren.

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

HTH,

+1

Auch beim Thema Best Practices; Ich verstehe, dass die Verwendung von Foo, Bar, FooBar ein gängiges Programmierparadigma ist, aber ehrlich gesagt musste ich dein Beispiel mehrmals lesen, um deine Absicht zu verstehen. : | – Dennis

+0

Hmm, ich verstehe. Ich werde dies in Zukunft berücksichtigen. –

Verwandte Themen