Должен ли я использовать интерфейс или фабрику (и интерфейс) для кроссплатформенной реализации? - PullRequest
5 голосов
/ 08 июня 2010

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

Пример Б:

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

Какой вариант лучше, например, A, B, ни один, или «это зависит»?

Ответы [ 5 ]

5 голосов
/ 08 июня 2010

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

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

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

0 голосов
/ 08 июня 2010

Фабрика - более чистое решение, поскольку вам не нужно внедрять каждый элемент интерфейса в оболочку 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
    }
}

HTH,

0 голосов
/ 08 июня 2010

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

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

Например,

// 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 или Castle Windsor 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 голосов
/ 08 июня 2010

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

0 голосов
/ 08 июня 2010

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