2009-05-08 3 views
6

При реализации фабрики или простой фабрики, что будет против использования типа вместо Enum для указания класса для создания экземпляра?Factory Pattern: перечисления или типы?

Например

public class SimpleFactory 
{ 
public static ITest Create(Type type) 
{ 
    if (type == typeof(ConcreteTest1)) 
    return new ConcreteTest1(); 
    if (type == typeof(ConcreteTest2)) 
    return new ConcreteTest2(); 

    throw new Exception("Invalid type"); 
} 
} 

ответ

15

Использования перечисления является более жестким, что означает, что менее вероятно, что пользователь будет пытаться использовать свой завод с неподдерживаемым типом.

Я считаю, что при определении API полезно делать все возможное, чтобы препятствовать шаблонам использования, которые будут вызывать исключения. Разрешение «Тип» в данном случае открывает миллионы способов называть вашу функцию, что приведет к:

throw new Exception("Invalid type"); 

Использование перечисления устранит это. Единственный способ, которым мог бы перебрасывать enum, был бы, если бы пользователь сделал что-то непонятное.

+2

Я определенно согласен! Ограничения времени компиляции намного проще применять и упрощать использование. – jeremyalan

2

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

ИМХО лучший подход использует наследование, один конкретный заводский класс для каждого конкретного типа.

+0

Как вы определяете, какую конкретную фабрику использовать? Вам все еще нужны вышеупомянутые условные обозначения? – theringostarrs

+0

Если вы используете класс типа FactoryLoader, который содержит указанные выше условия, так что логика удаляется с самих конкретных заводов, как избежать нарушения принципа open/closed при добавлении нового условного/типа фабрики в логику FactoryLoader? Разве мы не просто перемещаем проблему в другом месте? – theringostarrs

+0

Я предполагаю, что контейнер IoC позаботится об этом, но как бы вы сделали это чистым способом? – theringostarrs

1

Если вы хотите создать по типу, вы можете просто использовать Activator.CreateInstance(Type t). Оберните его в методе шаблонов, чтобы ограничить его интерфейсом, что-то вроде Create<T> where T:ITest.

2

Я бы предпочел использовать общее ограничение по той причине, что наличие перечисления только для указания того, какой именно объект вам кажется лишним, и с использованием типа, описанного вами, вы нарушаете Open/Closed принцип. То, что я сделал бы иначе, чем то, что вы там сделали, сдерживает ваш тип, так что могут быть переданы только допустимые типы.

Я приведу пример в C# с использованием дженериков.

public class SimpleFactory 
{ 
public static ITest Create<T>() 
    where T: ITest, new() 
{ 
    return new T(); 
} 
} 

Тогда вы бы реализовать IConcreteTest как с ConcreteTest1 и ConcreteTest2, и вы можете использовать ваш завод, как это:

ConcreteTest1 test1 = SimpleFactory.Create<ConcreteTest1>(); 
+0

Как передать тип в общий метод, исправить проблему? Разве это все еще не требует какого-либо расширенного знания T вызывающим? – jeremyalan

+0

Да, но в своем примере он уже знает тип, он передает его в метод. Единственное различие заключается в том, что я использую его как общий, поэтому вы можете сделать что-то вроде SimpleFactory.Create (); – Joseph

+0

Он не дал реализацию typeof, поэтому мы не можем быть уверены, что он заранее знает типы и может перейти к вызову. – Rohit

1

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

Использование информации о типе, извлеченной из производного типа, по-прежнему требует, чтобы вызывающий пользователь имел некоторые интимные знания о том, какой тип он хочет создать, что затрудняет обновление и обслуживание. Подменяя тип Enum (или строку, int и т. Д.), Вы можете обновить завод, не обновляя вызывающий код, чтобы знать о новых производных типах.

Я полагаю, можно утверждать, что имя типа может быть прочитано в виде строки из файла конфигурации, базы данных и т. Д. И информации о типе, определенной с помощью Reflections (в .NET) или RTTI (на C++), но Я думаю, что это лучший пример для простого использования строки типа в качестве вашего идентификатора, поскольку он будет эффективно служить той же цели.

4

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

Я бы создал фабрику для каждой иерархии классов. Например:

public abstract class Vehicle {} 
public class Car : Vehicle {} 
public class Truck : Vehicle {} 

public class VehicleFactory 
{ 
    public Vehicle CreateVehicle<T>() where T : Vehicle 
    { 
     // Get type of T and delegate creation to private methods 
    } 
}