2010-06-08 2 views
5

Пример A:Должен ли я использовать интерфейс или фабрику (и интерфейс) для кросс-платформенной реализации?

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

Пример 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(); 
} 

Какой лучший вариант, например, А, В, ни, или "оно не зависит от"?

ответ

0

Проблема с A заключается в том, что вы должны реализовать каждый метод IFoo в Foo. Это неважно, если есть только пара, но это боль, если их десятки. Если вы работаете с языком, который поддерживает фабричные методы, такие как закручивание, то вы могли бы поставить фабричный метод в 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

Интересный ответ, но я использую C#, но завод действительно еще вариант. Интересно, есть ли какие-либо явные недостатки использования фабрики в этом сценарии. –

0

Интерфейсы используются, когда это возможно, что может существовать множество реализаций одного функционального набора , Это звучит так, как будто это относится к вашему конкретному сценарию.

С точки зрения ваших примеров, я определенно рулон с B, его проще в обслуживании. Внутри отдельных классов [и/или методов] внедряется слишком много общей логики [т.е. обнаружения платформы]. Если вы хотите создать свой собственный класс Factory, попробуйте обобщить его [через общий метод Resolve<IType>() или что-то], в отличие от метода \ class для интерфейса.

Например,

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

Однако вместо того, реализовать свой собственный шаблон Factory, вы можете исследовать альтернативные реализации, такие как MS-х Unity2.0 или замок Виндзор CastleWindsorContainer. Они просты в настройке и потреблении.

В идеале,

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

О, полагаю, я должен крикнуть Ninject и StructureMap тоже. Я просто не так хорошо знаком с ними, как с Unity или CastleWindsor.

0

Если вы спросите меня, что B лучше, поскольку Foo сам не должен делать никаких переключений на платформе. Почему это имеет значение? Ну, так как вы, вероятно, захотите протестировать все компоненты отдельно - Foo с «test» IFoo, FooPlatformA отдельно на платформе A и FooPlatformB на платформе B. Если вы придерживаетесь выбора внутри Foo, вам нужно проверить Foo как на A, так и на B, а не на только разные IFoos. Делает компоненты более сложными без видимых причин.

5

Я бы сказал, что ваш явный заводской вариант (вариант B), как правило, лучше.

В вашем первом примере ваш класс Foo эффективно выполняет две работы, это фабрика, и это прокси. Две работы, один класс, заставляют меня беспокоиться.

Ваш второй вариант возлагает на клиента большую ответственность: им нужно знать, чтобы использовать фабрику, но это такая широко используемая идиома, которую я думаю, что это не трудно понять.

+0

Отличный ответ. Честно говоря, 4 функции опрокинули меня по краю. Повторное внедрение моей модели proxy/wrapper/factory в чистую чистую фабрику, когда мы говорим. В соответствующей заметке, пожалуйста, вы можете передать комментарий к классу CArch в синергии (он демонстрирует нечто похожее на пример A): http://synergy2.svn.sourceforge.net/viewvc/synergy2/trunk/lib/arch/CArch. cpp? view = markup –

+0

Спасибо. Я рассматриваю этот пример как очень фокусирующийся на создании прокси. С точки зрения клиента используется простой объект архитектуры, мы тратим усилия на создание этих прокси-методов, чтобы сделать жизнь клиента простой. Заводской метод тривиален. Если мы начнем иметь множество вариантов заводской логики, и особенно если нам удастся воспользоваться шаблоном Abstract Factory, я бы предпочел реорганизовать этот заводский код. Прямо сейчас мы уделяем наибольшее внимание простоте клиента. – djna

0

Завод является более чистым решением, так как вы не реализуете каждый член интерфейса в обертке class Foo : IFoo. Представьте себе, что каждый раз, когда вы изменяете интерфейс IFoo, вам нужно обновить обертку. При программировании, в зависимости от ваших целей, старайтесь как можно больше учитывать ремонтопригодность.

Доступны ли все «платформы» или только один из них? Разве единственная разница между платформами - логика? Думая с точки зрения разработчика игр, я бы использовал #defines для реализации этого.

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

НТН,

+1

Также по теме лучших практик; Я понимаю, что использование Foo, Bar, FooBar - это общая парадигма программирования, но честно пришлось несколько раз прочитать ваш пример, чтобы понять ваши намерения. : | – Dennis

+0

Хм, я вижу. Я расскажу об этом в будущем. –

Смежные вопросы