Я знаю, что подобные вопросы задавали и раньше. Я много читал об этом в течение последних двух дней и думаю, что теперь могу понять различия с точки зрения дизайна и потока кода. Что меня беспокоит, так это то, что кажется, что оба шаблона могут решить один и тот же набор проблем без реальной причины выбирать тот или иной. Пока я пытался разобраться в этом сам, я попытался реализовать небольшой пример (начиная с того, что я нашел в книге «Сначала голова: шаблоны проектирования»). В этом примере я дважды пытался решить одну и ту же проблему: один раз с использованием только «шаблона фабричного метода», а другой - с использованием «абстрактного фабричного шаблона». Я покажу вам код, а затем сделаю несколько комментариев и вопрос.
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
(поскольку он создает только один тип объекта, Пиццу) и «Шаблон абстрактной фабрики» для IngredientFactory
. В любом случае, другой принцип дизайна гласит, что вам следует «отдавать предпочтение композиции, а не наследованию», что предполагает, что я всегда должен использовать «абстрактный фабричный шаблон».
Мой вопрос: каковы причины, по которым я должен в первую очередь выбрать «Шаблон фабричного метода»?
Давайте взглянем на первую реализацию, ту, которая использует шаблон фабричного метода. Джесси ван Ассен предположил, что это шаблон метода Template вместо шаблона метода Factory. Я не уверен, что это правильно.
Мы можем разбить первую реализацию на две части: первая связана с Pizza
, а вторая - с PizzaStore
.
1) в первой части Пицца
- это клиент, который зависит от какого-то конкретного теста и соуса. Чтобы отделить пиццу от конкретных объектов, я использовал в классе Pizza
ссылку только на интерфейсы ( IDough
и ISauce
), и я разрешил Подклассы Pizza
определяют, какой бетон Тесто
и Соус
выбрать. На мой взгляд, это полностью соответствует определению шаблона метода Factory:
Определите интерфейс для создания объекта, но позвольте подклассам решать, какой класс создать. Метод Factory позволяет классу отложить создание экземпляра до подклассов.
2) во второй части PizzaStore
является клиентом и зависит от конкретной пиццы
. Я применил те же принципы, о которых говорилось выше.
Итак, чтобы лучше выразить (я надеюсь) то, что я действительно не понимаю, это почему сказано, что:
Шаблон Factory Method отвечает за создание продуктов, принадлежащих к одному семейству, тогда как шаблон Abstract Factory имеет дело с несколькими семействами продуктов.
Как вы видите из моих примеров (при условии, что они верны :-)), с обоими шаблонами можно работать одинаково.