Пример 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();
}
Который является более оптимальным вариантом, пример A, B, ни один, или "он зависит"?
Проблема с 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}
Если вы спросите меня, то B намного лучше - поскольку Foo не нужно делать никаких переключений на платформе. Почему это важно? Ну, поскольку вы, вероятно, хотите тестировать все компоненты отдельно - Foo с "тестовым" IFoo, FooPlatformA отдельно на платформе A и FooPlatformB на платформе B. Если вы поместите выбор внутрь Foo, вам нужно будет тестировать Foo на A и B, а не только на разных IFoo. Это делает компоненты более связанными без видимой причины.
Фабрика - более чистое решение, поскольку у вас нет реализации каждого члена интерфейса в оболочке класс 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,
Я бы сказал, что ваш явный заводской вариант (вариант B) в целом лучше.
В вашем первом примере ваш класс Foo эффективно выполняет две задачи: это фабрика и прокси. Две работы, одно занятие доставляют мне неудобства.
Ваш второй вариант возлагает на клиента немного больше ответственности: он должен знать, чтобы использовать фабрику, но это настолько широко используемая идиома, что я думаю, ее нетрудно понять.
Интерфейсы используются, когда возможно, что существует несколько реализаций одного функционального набора. Похоже, это применимо к вашему конкретному сценарию.
Если говорить о ваших примерах, я бы определенно выбрал B, его легче поддерживать. A встраивает слишком много общей логики [например, определение платформы] в отдельные классы [и / или методы]. Если вы хотите создать свой собственный класс Factory, попробуйте обобщить его [с помощью универсального метода Resolve
или чего-то еще], а не метода \ класса для каждого интерфейса.
Например,
// 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.