Как выбрать между шаблоном фабричного метода и абстрактным фабричным шаблоном

Я знаю, что подобные вопросы задавали раньше. Я много читал об этом в течение последних двух дней и думаю, что теперь могу понять различия с точки зрения дизайна и потока кода. Что меня беспокоит, так это то, что кажется, что оба шаблона могут решить один и тот же набор проблем без реальной причины выбирать тот или иной. Пока я пытался разобраться в этом сам, я попытался реализовать небольшой пример (начиная с того, что я нашел в книге «Сначала голова: шаблоны проектирования»). В этом примере я дважды пытался решить одну и ту же проблему: один раз с использованием только «шаблона метода фабрики», а другой - с использованием шаблона «абстрактная фабрика». Я покажу вам код, а затем сделаю несколько комментариев и вопрос.

Общие интерфейсы и классы

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 работает с несколькими семействами продуктов.

Как вы видите из моих примеров (при условии, что они верны :-)), вы можете использовать оба шаблона одинаково.


person Community    schedule 21.01.2012    source источник


Ответы (2)


Во-первых, две цитаты из книги шаблонов проектирования GoF:

Абстрактная фабрика часто реализуется с помощью фабричных методов.

Фабричные методы часто вызываются шаблонными методами.

Таким образом, это не вопрос выбора между фабричными методами и абстрактными фабриками, потому что последние могут быть (и обычно реализуются) первыми.

Концепция абстрактных фабрик (как намекнул Амир) состоит в том, чтобы сгруппировать создание нескольких конкретных классов, которые всегда идут вместе. В вашем примере это должны быть разновидности пищевых компонентов NY, а не разновидности KN.

Но если вы хотите разрешить смешивание и сопоставление (что не так с пиццей с тестом KN и соусом NY?), Тогда абстрактные фабрики не ваш ответ. В этом случае каждый подкласс Pizza должен решить, какие конкретные классы он хочет создать.

Если вы не хотите допускать такого рода смешивания, вам следует использовать абстрактные фабрики.

person Asaf    schedule 22.01.2012
comment
Извините, мне, вероятно, нужно нуб, чтобы полностью понять, что вы имеете в виду, но я не понимаю, как абстрактные фабрики не позволяют то, что вы называете смешиванием и сопоставлением. Вы всегда можете определить другую фабрику, унаследованную от IIngredientFactory и возвращающую конкретный тип, который вы хотите смешать. - person ; 22.01.2012
comment
Эй, я сам далек от того, чтобы быть экспертом по шаблонам дизайна :) Технически вы можете делать все, что угодно, но это не будет считаться абстрактной фабрикой. Идея абстрактной фабрики - это объединение конкретных объектов в семьи. Если вы действительно хотите смешивать, я бы вообще не стал делать этого с подклассами, так как это может привести к раздуванию классов. Я бы, вероятно, использовал какой-то конструктор - класс Pizza указывал бы, какой тип компонента он хочет (тесто NY, соус KN) с каким-то идентификатором, а строитель будет отвечать за создание конкретных объектов. - person Asaf; 22.01.2012

Если вам нужна пара связанных фабричных методов, которые должны принимать одинаковые решения, тогда лучше сгруппировать их в абстрактные фабрики.

Я скажу, что ваша первая реализация не является фабричным методом. Фабричные методы не являются абстрактными, и у них есть параметры, которые решают, что в соответствии с ними создавать.

person Amir Pashazadeh    schedule 21.01.2012
comment
Спасибо, что нашли время ответить. Я думаю, что в Factory Method методы должны быть абстрактными, потому что вы хотите реализовать их в подклассах. Это требование шаблона. Что касается параметров, с другой стороны, я не совсем уверен, что это требование. - person ; 22.01.2012
comment
Используемый вами шаблон является методом шаблона, а не фабричным методом. - person Jesse van Assen; 22.01.2012
comment
@JessevanAssen: спасибо, вы, наверное, просветили меня, и теперь даже ответ Амира имеет для меня больше смысла. Я не знал о шаблоне метода Template, я собираюсь изучить его еще немного и позже опубликую кое-что. - person ; 22.01.2012
comment
мм. Я еще не уверен, я немного отредактировал, чтобы лучше объяснить, что я имею в виду. Надеюсь, у вас будет время прочитать его и поделиться своими мыслями. Спасибо. - person ; 22.01.2012