Как выбрать между шаблоном фабричного метода и абстрактным фабричным шаблоном - PullRequest
15 голосов
/ 21 января 2012

Я знаю, что подобные вопросы задавались ранее. Я много читал об этом в последние пару дней и думаю, что теперь я могу понять разницу с точки зрения дизайна и потока кода. Что меня беспокоит, так это то, что оба шаблона могут решить один и тот же набор проблем без реальной причины выбора того или иного. Пока я пытался понять это сам, я попытался реализовать небольшой пример (начиная с того, который я нашел в книге «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 - для нескольких семейств продуктов.

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

Ответы [ 2 ]

9 голосов
/ 22 января 2012

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

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

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

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

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

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

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

2 голосов
/ 22 января 2012

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...