Как выбрать шаблон метода Factory и шаблон Abstract Factory
Я знаю, что подобные вопросы задавали раньше. Я много читал об этом в течение последних двух дней, и я думаю, что теперь я могу понять различия в плане дизайна и потока кода. Меня это беспокоит, что оба образца могут решить один и тот же набор проблем без реальной причины выбирать тот или иной.
Хотя я пытался понять это сам, я попытался выполнить небольшой пример (начиная с того, который я нашел в книге "Head First: Design patterns" ).
В этом примере я попытался решить одну и ту же проблему дважды: одно время использовало только шаблон метода Factory, а другой - "Абстрактный шаблон Factory" . Я покажу вам код, а затем сделаю несколько комментариев и вопрос.
Общие интерфейсы и классы
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;
}
}
}
чистый Абстрактный шаблон Factory
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" для PizzaStore
(поскольку он только строит один тип объекта, Pizza) и "Абстрактный шаблон Factory" для IngredientFactory
. Во всяком случае, другой принцип проектирования, заявляет, что вы должны "одобрить композицию над наследованием", которая предполагает, что я всегда должен использовать "Абстрактный шаблон Factory" .
Мой вопрос: каковы причины, по которым я должен выбрать "шаблон метода Factory"?
ИЗМЕНИТЬ
Посмотрим на первую реализацию, которая использует шаблон метода Factory. Джесси ван Асен предположил, что это шаблон шаблона шаблона вместо шаблона метода Factory. Я не уверен в этом.
Мы можем разбить первую реализацию на две части: первую, которая имеет дело с Pizza
, а вторая - с PizzaStore
.
1) в первой части Pizza
есть клиент, который зависит от какого-то конкретного теста и соуса. Чтобы отделить пиццу от конкретных объектов, я использовал в классе Pizza
ссылку только на интерфейсы (IDough
и ISauce
), и я позволяю подклассам Pizza
решать, какие конкретные Dough
и Sauce
выбрать, Для меня это идеально подходит для определения шаблона метода Factory:
Определите интерфейс для создания объекта, но пусть подклассы решают, какой класс необходимо создать. Метод Factory позволяет отладчику класса добавлять подклассы.
2) во второй части PizzaStore
является клиентом и зависит от конкретного Pizza
. Я применил те же принципы, о которых говорилось выше.
Итак, чтобы выразить лучше (я надеюсь), что я действительно не понимаю, почему говорится, что:
Factory Шаблон метода отвечает за создание продуктов, принадлежащих одному семейству, в то время как абстрактный шаблон Factory имеет дело с несколькими семействами продуктов.
Как вы видите из моих примеров (при условии, что они правы:-)), вы можете использовать тот же материал с обоими шаблонами.
Ответы
Ответ 1
Во-первых, 2 цитаты из книги шаблонов дизайна GoF:
"Аннотация Factory часто реализуется с помощью методов Factory.
" Factory Методы часто вызывают методы шаблонов."
Итак, это не вопрос выбора между Factory Методами и абстрактными фабриками, потому что позже может быть (и обычно) реализовано первым.
Концепция абстрактных фабрик (как намек Амира) состоит в том, чтобы сгруппировать создание нескольких конкретных классов, которые всегда идут вместе. В вашем примере они должны быть разновидностями продуктов питания в Нью-Йорке, а не KN.
Но если вы хотите разрешить микс и матч (что не так с пиццей с тестом KN и NY souce?), то абстрактные фабрики - это не ваш ответ. В этом случае каждый подкласс Pizza должен решить, какие конкретные классы он хочет создать.
Если вы не хотите разрешать подобные микширования, вы должны пойти с абстрактными фабриками.
Ответ 2
Если вам нужна пара связанных методов Factory, которые должны принимать одинаковые решения, тогда лучше группировать их в абстрактных фабриках.
Я скажу, что ваша первая реализация - это не метод Factory. Factory методы не абстрактны, и у них есть параметры, которые решают, что им следует создавать.