Я знаю, что подобные вопросы задавались ранее. Я много читал об этом в последние пару дней и думаю, что теперь я могу понять разницу с точки зрения дизайна и потока кода. Что меня беспокоит, так это то, что оба шаблона могут решить один и тот же набор проблем без реальной причины выбора того или иного.
Пока я пытался понять это сам, я попытался реализовать небольшой пример (начиная с того, который я нашел в книге «Head First: Design pattern»).
В этом примере я пытался решить одну и ту же проблему дважды: один раз, используя только «шаблон фабричного метода», а другой - «абстрактный шаблон фабрики». Я покажу вам код, а затем сделаю несколько комментариев и вопрос.
Общие интерфейсы и классы
public interface IDough { }
public interface ISauce { }
public class NYDough : IDough { }
public class NYSauce : ISauce { }
public class KNDough : IDough { }
public class KNSauce : ISauce { }
Шаблон метода Pure Factory
// 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();
}
}
Если бы я использовал определения шаблонов, я бы выбрал «Шаблон фабричного метода» для PizzaStore
(поскольку он строит только один тип объекта, Пицца) и «Абстрактный шаблон фабрики» для IngredientFactory
. В любом случае, другой принцип разработки гласит, что вы должны «отдавать предпочтение композиции, а не наследованию», что предполагает, что я всегда должен использовать «Абстрактный шаблон фабрики».
У меня вопрос: по каким причинам я должен выбрать «Шаблон фабричного метода»?
EDIT
Давайте посмотрим на первую реализацию, которая использует шаблон метода Factory. Джесси ван Ассен предположил, что это шаблон метода Template вместо шаблона фабричного метода. Я не уверен, что это правильно.
Мы можем разбить первую реализацию на две части: первая, которая имеет дело с Pizza
, и вторая, которая имеет дело с PizzaStore
.
1) в первой части Pizza
это клиент, который зависит от какого-то конкретного теста и соуса. Чтобы отделить Pizza от конкретных объектов, я использовал в классе Pizza
ссылку только на интерфейсы (IDough
и ISauce
), и я позволил подклассам Pizza
решить, какие конкретные Dough
и Sauce
выбирать. Для меня это идеально согласуется с определением шаблона метода Фабрики:
Определите интерфейс для создания объекта, но пусть подклассы решают, какой класс создать. Метод Factory позволяет классу откладывать создание экземпляров для подклассов.
2) во второй части PizzaStore
является клиентом и зависит от конкретного Pizza
. Я применил те же принципы, которые обсуждались выше.
Итак, чтобы выразить лучше (я надеюсь), что я не понимаю, почему говорится, что:
Шаблон Factory Factory отвечает за создание продуктов, принадлежащих одному семейству, а шаблон Abstract Factory - для нескольких семейств продуктов.
Как вы видите из моих примеров (при условии, что они правы :-)), вы можете делать то же самое с обоими шаблонами.