Как выбрать шаблон метода 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 методы не абстрактны, и у них есть параметры, которые решают, что им следует создавать.