2010-07-28 1 views
21

Возьмите следующую классический образец фабрики:Factory Pattern, но с объектными Параметрами

public interface IPizza 
{ 
    decimal Price { get; } 
} 

public class HamAndMushroomPizza : IPizza 
{ 
    decimal IPizza.Price 
    { 
     get 
     { 
      return 8.5m; 
     } 
    } 
} 
public abstract class PizzaFactory 
{ 
    public abstract IPizza CreatePizza(ItalianPizzaFactory.PizzaType pizzaType); 
} 

public class ItalianPizzaFactory : PizzaFactory 
{ 
    public enum PizzaType 
    { 
     HamMushroom, 
     Deluxe, 
     Hawaiian 
    } 

    public override IPizza CreatePizza(PizzaType pizzaType) 
    { 
     switch (pizzaType) 
     { 
      case PizzaType.HamMushroom: 
       return new HamAndMushroomPizza(); 
      case PizzaType.Hawaiian: 
       return new HawaiianPizza(); 
      default: 
       throw new ArgumentException("The pizza type " + pizzaType + " is not recognized."); 
     } 
    } 
} 

Что делать, если один (или несколько) из бетона пица требует параметра специфичного для конкретной реализации в строительстве. Например, скажем, фабрике HamAndMushroom требуется параметр, называемый MushroomType, и этот параметр должен был бы создать экземпляр объекта?

+2

Ухм ... может быть, ваш подход к проблеме неправильный. Поскольку все типы Pizza отличаются только от поля данных WRT, один тип Pizza будет делать с данными в БД. – Mau

ответ

18

Вы можете добавить параметры к методу (-ам) создателя на вашем заводе-изготовителе. Однако, если число параметров становится выше (для меня это будет более 2-3), и особенно если некоторые или все эти параметры являются необязательными с разумными значениями по умолчанию, вы можете вместо этого перевести завод в Builder.

Это может быть особенно уместно для пиццы, где у вас обычно есть одна и та же корка, только с разными (комбинациями) начинок. Строитель очень близко моделирует общий способ упорядочивания, например. «пицца с салями, помидорами, кукурузой и двойным сыром». OTOH для «предопределенных» пицц, вы можете определить методы вспомогательных фабрик, например. createMargaritaPizza или createHawaiiPizza, которые затем внутренне используют строитель для создания пиццы с начинками, характерными для такого пиццы.

+0

Кроме того, если Пицца требует других объектов, живите Духовкой или специально StoneOven, я бы рекомендовал использовать и IoC, так как они являются огогональными объектами (настраиваемые) начинки ;-) – cRichter

+1

Я согласен с паттерном Peter, Builder более подходящий шаблон для сценария, описанного ОП. Он может захотеть взглянуть на композитный рисунок, а также на обработку одного порядка нескольких пиццы. –

+7

Вы, ребята, заставляете меня голодать :) –

0

Вам понадобится добавить еще один метод CreatePizza() для этого заводского класса. И это будет означать, что пользователи фабрики не смогут создавать эти типы пиццы, если они специально не используют экземпляр класса HamAndMushroomPizzaFactory. Если у них просто есть ссылка PizzaFactory, они могут вызывать только версию без параметров и не смогут создавать ветчину и грибную пиццу в целом.

0

Вы можете использовать отражение:

using System.Reflection; 

// ... 

public override IPizza CreatePizza(PizzaType pizzaType, params object[] parameters) { 
      return (IPizza) 
        Activator.CreateInstance(
         Assembly 
          .GetExecutingAssembly() 
          .GetType(pizzaType.ToString()), 
         parameters); 
     } 
+2

Мне не нравится этот подход. Если вы измените конструктор класса, вы должны изменить все вызовы на завод (и вы получите только ошибки времени выполнения). Это стирает преимущество фабрики: скрывает строительство объектов. – onof

+7

Я просто вырвал «IPizza» на всей клавиатуре после прочтения «использования отражения». хи хи. –

+0

:-D Я знаю, это побеждает цель ... – Mau

0

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

0

Прежде всего, мне кажется странным, что абстрактный класс PizzaFactory содержит абстрактный общий метод CreatePizza, который принимает параметр более конкретного типа ItalianPizzaFactory.PizzaType.

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

public struct PizzaDefinition 
{ 
    public readonly string Tag; 
    public readonly string Name; 
    public readonly string Description; 
    public PizzaDefinition(string tag, string name, string description) 
    { 
     Tag = tag; Name = name; Description = description; 
    } 
} 

public abstract class PizzaFactory 
{ 
    public abstract IEnumerable<PizzaDefinition> GetMenu(); 
    public abstract IPizza CreatePizza(PizzaDefinition pizzaDefinition); 
} 


public class ItalianPizzaFactory : PizzaFactory 
{ 
    public enum PizzaType 
    { 
     HamMushroom, 
     Deluxe, 
     Hawaiian 
    }  

    public override IEnumerable<PizzaDefinition> GetMenu() 
    { 
     return new PizzaDefinition[] { 
      new PizzaDefinition("hm:mushroom1,cheese3", "Ham&Mushroom 1", "blabla"), 
      new PizzaDefinition("hm:mushroom2,cheese1", "Ham&Mushroom 2", "blabla"), 
      new PizzaDefinition("dx", "Deluxe", "blabla"), 
      new PizzaDefinition("Hawaian:shrimps,caramel", "Hawaian", "blabla") 
     }; 
    } 

    private PizzaType ParseTag(string tag, out object[] options){...} 

    public override IPizza CreatePizza(PizzaDefinition pizzaDefinition) 
    { 
     object[] options; 
     switch (ParseTag(pizzaDefinition.Tag, out options)) 
     { 
      case PizzaType.HamMushroom: 
       return new HamAndMushroomPizza(options); 
      case PizzaType.Hawaiian: 
       return new HawaiianPizza(); 
      default: 
       throw new ArgumentException("The pizza" + pizzaDefinition.Name + " is not on the menu."); 
     } 
    } 
} 

Как вы видите, метод ParseTag() может быть произвольной сложности, разбор обычного текста или зашифрованное значение. Или поле тега может быть простым int, который отображается внутри таблицы рецептов пиццы, с целыми разными рецептами даже для слегка измененного содержимого пиццы.

+1

Я не думаю, что описание пиццы в строчке - это правильный способ сделать что-то. Лучше было бы, чтобы PizzaDefinition был классом, который можно подклассифицировать. Более специализированные пиццы могут иметь более специализированные определения пиццы. – siride

+0

@siride: Правильно, я только что указал подход. Класс PizzaDefinition может быть очень сложным и иметь некоторые методы, которые помогут вам выбрать один из вариантов и так далее. Но этот конкретный пример по-прежнему стоит учитывать, поскольку он очень надежный и в то же время дает то, что вы можете пожелать - список доступных пицц и способ создания каждого из них. – ULysses

0

Вы можете попробовать что-то вроде этого:

interface IPizza 
{ 
} 

class Pizza1 : IPizza 
{ 
    public Pizza1(Pizza1Parameter p) 
    { 
    } 
} 

class Pizza2 : IPizza 
{ 
    public Pizza2(Pizza2Parameter p) 
    { 
    } 
} 

interface IPizzaParameter 
{ 
    object Type { get; set; } 
} 

class Pizza1Parameter : IPizzaParameter 
{ 
    public object Type { get; set; } 
} 

class Pizza2Parameter : IPizzaParameter 
{ 
    public object Type { get; set; } 
} 

static class PizzaFactory 
{ 
    public enum PizzaType 
    { 
    Pizza1, 
    Pizza2, 
    } 

    public static IPizza CreatePizza(PizzaType type, IPizzaParameter param) 
    { 
    switch (type) 
    { 
     case PizzaType.Pizza1: 
     return new Pizza1(param as Pizza1Parameter); 
     case PizzaType.Pizza2: 
     return new Pizza2(param as Pizza2Parameter); 
    } 

    throw new ArgumentException(); 
    } 
} 

class Program 
{ 
    static void Main() 
    { 
    var param1 = new Pizza1Parameter(); 
    var p1 = PizzaFactory.CreatePizza(PizzaFactory.PizzaType.Pizza1, param1); 
    } 
} 

ИМХО концепция завода с реализацией конкретных параметров выглядит так.

0

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

Кроме того, когда параметры «требуются», я также думаю, что Builder теряет свое очарование.

В этом случае я могу комбинировать завод с «Объектом параметров», который уменьшил бы количество параметров, необходимых для передачи в статические заводские методы, и это могло бы сделать логику создания более читабельной и опрятной, чем использование строитель. Но, конечно, этот объект параметра также необходимо создать, но, по крайней мере, он будет в одной, единой форме в вашем приложении.