Я знаю, что подобные вопросы задавали раньше. Я много читал об этом в течение последних двух дней и думаю, что теперь могу понять различия с точки зрения дизайна и потока кода. Что меня беспокоит, так это то, что кажется, что оба шаблона могут решить один и тот же набор проблем без реальной причины выбирать тот или иной. Пока я пытался разобраться в этом сам, я попытался реализовать небольшой пример (начиная с того, что я нашел в книге «Сначала голова: шаблоны проектирования»). В этом примере я дважды пытался решить одну и ту же проблему: один раз с использованием только «шаблона метода фабрики», а другой - с использованием шаблона «абстрактная фабрика». Я покажу вам код, а затем сделаю несколько комментариев и вопрос.
Общие интерфейсы и классы
public interface IDough { }
public interface ISauce { }
public class NYDough : IDough { }
public class NYSauce : ISauce { }
public class KNDough : IDough { }
public class KNSauce : ISauce { }
Шаблон метода чистой фабрики
// pure Factory method pattern
public abstract class Pizza
{
protected IDough Dough { get; set; }
protected ISauce Sauce { get; set; }
protected abstract IDough CreateDough();
protected abstract ISauce CreateSauce();
public void Prepare()
{
Dough = CreateDough();
Sauce = CreateSauce();
// do stuff with Dough and Sauce
}
public void Bake() { }
public void Cut() { }
public void Box() { }
}
public class NYCheesePizza : Pizza
{
protected override IDough CreateDough()
{
return new NYDough();
}
protected override ISauce CreateSauce()
{
return new NYSauce();
}
}
public class KNCheesePizza : Pizza
{
protected override IDough CreateDough()
{
return new KNDough();
}
protected override ISauce CreateSauce()
{
return new KNSauce();
}
}
public abstract class PizzaStore
{
public void OrderPizza(string type)
{
Pizza pizza = CreatePizza(type);
pizza.Prepare();
pizza.Bake();
pizza.Cut();
pizza.Box();
}
public abstract Pizza CreatePizza(string type);
}
public class NYPizzaStore : PizzaStore
{
public override Pizza CreatePizza(string type)
{
switch (type)
{
case "cheese":
return new NYCheesePizza();
default:
return null;
}
}
}
public class KNPizzaStore : PizzaStore
{
public override Pizza CreatePizza(string type)
{
switch (type)
{
case "cheese":
return new KNCheesePizza();
default:
return null;
}
}
}
чистый абстрактный завод шаблон
public interface IIngredientFactory
{
IDough createDough();
ISauce createSauce();
}
public class NYIngredientFactory : IIngredientFactory
{
public IDough createDough()
{
return new NYDough();
}
public ISauce createSauce()
{
return new NYSauce();
}
}
public class KNIngredientFactory : IIngredientFactory
{
public IDough createDough()
{
return new KNDough();
}
public ISauce createSauce()
{
return new KNSauce();
}
}
public class Pizza
{
IDough Dough { get; set; }
ISauce Sauce { get; set; }
IIngredientFactory IngredientFactory { get; set; }
public Pizza(IIngredientFactory ingredientFactory)
{
IngredientFactory = ingredientFactory;
}
public void Prepare()
{
Dough = IngredientFactory.createDough();
Sauce = IngredientFactory.createSauce();
}
public void Bake() { }
public void Cut() { }
public void Box() { }
}
public interface IPizzaFactory
{
Pizza CreatePizza(string type);
}
public class NYPizzaFactory : IPizzaFactory
{
public Pizza CreatePizza(string type)
{
switch (type)
{
case "cheese":
return new Pizza(new NYIngredientFactory());
default:
return null;
}
}
}
public class KNPizzaFactory : IPizzaFactory
{
public Pizza CreatePizza(string type)
{
switch (type)
{
case "cheese":
return new Pizza(new KNIngredientFactory());
default:
return null;
}
}
}
public class PizzaStore
{
IPizzaFactory PizzaFactory { get; set; }
public PizzaStore(IPizzaFactory pizzaFactory)
{
PizzaFactory = pizzaFactory;
}
public void OrderPizza(string type)
{
Pizza pizza = PizzaFactory.CreatePizza(type);
pizza.Prepare();
pizza.Bake();
pizza.Cut();
pizza.Box();
}
}
Если бы я использовал определения шаблонов, я бы выбрал «Factory Method Pattern» для PizzaStore
(поскольку он строит только один тип объекта, Pizza) и «Abstract Factory Pattern» для IngredientFactory
. В любом случае, другой принцип дизайна гласит, что вам следует «отдавать предпочтение композиции перед наследованием», что предполагает, что я всегда должен использовать «абстрактный фабричный шаблон».
Мой вопрос: каковы причины, по которым я должен в первую очередь выбрать «Шаблон заводского метода»?
РЕДАКТИРОВАТЬ
Давайте посмотрим на первую реализацию, которая использует шаблон метода Factory. Джесси ван Ассен предположил, что это шаблон метода Template вместо шаблона метода Factory. Я не уверен, что это правильно. Мы можем разбить первую реализацию на две части: первая касается Pizza
, а вторая - PizzaStore
.
1) в первой части Pizza
- это клиент, зависящий от какого-то конкретного теста и соуса. Чтобы отделить пиццу от конкретных объектов, я использовал в классе Pizza
ссылку только на интерфейсы (IDough
и ISauce
), и я позволил подклассам Pizza
решать, какой конкретный Dough
и Sauce
выбрать. Для меня это идеально соответствует определению шаблона метода Factory:
Определите интерфейс для создания объекта, но позвольте подклассам решать, какой класс создать. Метод Factory позволяет классу отложить создание экземпляра до подклассов.
2) во второй части PizzaStore
- это клиент, и он зависит от конкретного Pizza
. Я применил те же принципы, о которых говорилось выше.
Итак, чтобы лучше выразить (я надеюсь) то, что я действительно не понимаю, - это почему сказано следующее:
Шаблон Factory Method отвечает за создание продуктов, принадлежащих к одному семейству, а шаблон Abstract Factory работает с несколькими семействами продуктов.
Как вы видите из моих примеров (при условии, что они верны :-)), вы можете использовать оба шаблона одинаково.