Шаблоны проектирования: фабрика против фабрики, метод против абстрактной фабрики

Я читал шаблоны дизайна с веб-сайта

Там я читал о Фабрике, Фабричном методе и Абстрактной фабрике, но они настолько запутанные, что у меня нет четкого определения. Согласно определениям

Factory - создает объекты без предоставления клиенту логики создания экземпляров и ссылается на вновь созданный объект через общий интерфейс. Это упрощенная версия Factory Method

Factory Method - определяет интерфейс для создания объектов, но позволяет подклассам решать, какой класс создавать, и ссылается на вновь созданный объект через общий интерфейс.

Абстрактная фабрика - предлагает интерфейс для создания семейства связанных объектов без явного указания их классов.

Я также посмотрел другие потоки stackru, касающиеся абстрактной фабрики и фабричного метода, но нарисованные там диаграммы UML делают мое понимание еще хуже.

Может кто-нибудь, пожалуйста, скажите мне

  1. Чем эти три модели отличаются друг от друга?
  2. Когда использовать что?
  3. А также, если возможно, какие-либо примеры Java, связанные с этими шаблонами?

10 ответов

Все три типа Factory делают одно и то же: они "умный конструктор".

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

завод

Фабрика "исправлена", так как у вас есть только одна реализация без подклассов. В этом случае у вас будет такой класс:

class FruitFactory {

  public Apple makeApple() {
    // Code for creating an Apple here.
  }

  public Orange makeOrange() {
    // Code for creating an orange here.
  }

}

Вариант использования: Построить Apple или Orange слишком сложно, чтобы справиться с ним в конструкторе.

Фабричный метод

Фабричный метод обычно используется, когда у вас есть некоторая общая обработка в классе, но вы хотите изменить, какой вид фруктов вы фактически используете. Так:

abstract class FruitPicker {

  protected abstract Fruit makeFruit();

  public void pickFruit() {
    private final Fruit f = makeFruit(); // The fruit we will work on..
    <bla bla bla>
  }
}

... тогда вы можете повторно использовать общие функции в FruitPicker.pickFruit() путем реализации фабричного метода в подклассах:

class OrangePicker extends FruitPicker {

  @Override
  protected Fruit makeFruit() {
    return new Orange();
  }
}

Абстрактная Фабрика

Абстрактная фабрика обычно используется для таких вещей, как внедрение / стратегия зависимости, когда вы хотите иметь возможность создавать целое семейство объектов, которые должны быть "одного и того же типа" и иметь несколько общих базовых классов. Вот пример, связанный с фруктами. В данном случае мы хотим убедиться, что случайно не используем OrangePicker на Apple. Пока мы получаем наши фрукты и сборщик с одного завода, они будут совпадать.

interface PlantFactory {

  Plant makePlant();

  Picker makePicker(); 

}

public class AppleFactory implements PlantFactory {
  Plant makePlant() {
    return new Apple();
  }

  Picker makePicker() {
    return new ApplePicker();
  }
}

public class OrangeFactory implements PlantFactory {
  Plant makePlant() {
    return new Orange();
  }

  Picker makePicker() {
    return new OrangePicker();
  }
}

На основе этих изображений из книги « Шаблоны проектирования на C#, 2-е издание» Васкарана Саркара :

1. Простой заводской шаблон

Создает объекты, не открывая клиенту логику создания экземпляров.

      SimpleFactory simpleFactory = new SimpleFactory();
IAnimal dog = simpleFactory.CreateDog(); // Create dog
IAnimal tiger = simpleFactory.CreateTiger(); // Create tiger

2. Шаблон заводского метода

Определяет интерфейс для создания объектов, но позволяет подклассам решать, какой класс создавать.

      AnimalFactory dogFactory = new DogFactory(); 
IAnimal dog = dogFactory.CreateAnimal(); // Create dog

AnimalFactory tigerFactory = new TigerFactory();
IAnimal tiger = tigerFactory.CreateAnimal(); // Create tiger

3. Абстрактная фабрика паттернов (фабрика фабрик)

Abstract Factory предлагает интерфейс для создания семейства связанных объектов без явного указания их классов.

      IAnimalFactory petAnimalFactory = FactoryProvider.GetAnimalFactory("pet");
IDog dog = petAnimalFactory.GetDog(); // Create pet dog
ITiger tiger = petAnimalFactory.GetTiger();  // Create pet tiger

IAnimalFactory wildAnimalFactory = FactoryProvider.GetAnimalFactory("wild");
IDog dog = wildAnimalFactory .GetDog(); // Create wild dog
ITiger tiger = wildAnimalFactory .GetTiger();  // Create wild tiger

  1. Чем эти три модели отличаются друг от друга?

Factory: создает объекты без предоставления клиенту логики создания экземпляров.

Фабричный метод: определите интерфейс для создания объекта, но пусть подклассы решают, какой класс создать. Метод Factory позволяет классу откладывать создание экземпляров для подклассов

Абстрактная фабрика: предоставляет интерфейс для создания семейств связанных или зависимых объектов без указания их конкретных классов.

Шаблон AbstractFactory использует композицию, чтобы делегировать ответственность за создание объекта другому классу, в то время как шаблон разработки метода Factory использует наследование и опирается на производный класс или подкласс для создания объекта.

  1. Когда использовать что?

Фабрика: Клиенту просто нужен класс, и ему все равно, какую конкретную реализацию он получает.

Фабричный метод: Клиент не знает, какие конкретные классы ему потребуется создать во время выполнения, но просто хочет получить класс, который будет выполнять эту работу.

AbstactFactory: когда вашей системе нужно создать несколько семейств продуктов или вы хотите предоставить библиотеку продуктов без раскрытия деталей реализации.

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

  1. А также, если возможно, какие-либо примеры Java, связанные с этими шаблонами?

Фабрика и ФабрикаМетод

Намерение:

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

UML-диаграмма:

Product: определяет интерфейс объектов, создаваемых методом Factory.

ConcreteProduct: реализует интерфейс продукта

Создатель: объявляет фабричный метод

ConcreateCreator: Реализует метод Factory для возврата экземпляра ConcreteProduct.

Постановка задачи: Создайте Фабрику Игр, используя Фабричные Методы, которые определяют интерфейс игры.

Фрагмент кода:

Заводская модель. Когда использовать фабричные методы?

Сравнение с другими шаблонами творчества:

  1. Проектирование начинается с использования Factory Method (менее сложный, более настраиваемый, подклассы разрастаются) и развивается в сторону Abstract Factory, Prototype или Builder (более гибкий, более сложный), так как дизайнер обнаруживает, где необходима большая гибкость

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

Ссылки для дальнейшего чтения: Разработка шаблонов

Factory - отдельный класс Factory для создания сложного объекта.

Пример: класс FruitFactory для создания объекта Fruit

class FruitFactory{

public static Fruit getFruit(){...}

}

Фабричный метод - вместо целого отдельного класса для фабрики, просто добавьте один метод в этот класс как фабрику.

Пример:

Calendar.getInstance() (Java's Calendar)

Абстрактный Фабричный Метод - Фабрика Фабрики

Пример: допустим, мы хотим построить завод по производству компьютерных комплектующих. Таким образом, существует несколько типов компьютеров, таких как ноутбук, настольный компьютер, сервер.

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

ComputerTypeAbstractFactory.getComputerPartFactory(String computerType) ---> This will return PartFactory which can be one of these ServerPartFactory, LaptopPartFactory, DesktopPartFactory.

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

  Interface-> PartFactory. getComputerPart(String s), 
Implementations -> ServerPartFactory, LaptopPartFactory, DesktopPartFactory.

Usage:
new ComputerTypeAbstractFactory().getFactory(“Laptop”).getComputerPart(“RAM”)

РЕДАКТИРОВАТЬ: отредактировано, чтобы обеспечить точные интерфейсы для абстрактной фабрики в соответствии с возражениями в комментариях.

За этим ответом я обращаюсь к книге "Банда четырех".

В книге нет определений "Фабрика", "Простая фабрика" или "Виртуальная фабрика". Обычно, когда люди говорят о шаблоне "Фабрика", они могут говорить о чем-то, что создает определенный объект класса (но не о шаблоне "построитель"); они могут или не могут относиться к шаблонам "Factory Method" или "Abstract Factory". Любой может реализовать "Factory", хотя он этого не сделает, потому что это не формальный термин (имейте в виду, что некоторые люди \ компании \ сообщества могут иметь свой собственный словарь).

Книга содержит только определения "абстрактной фабрики" и "фабричного метода".

Вот определения из книги и краткое объяснение того, почему и то, и другое может сбивать с толку. Я опускаю примеры кода, потому что вы можете найти их в других ответах:

Фабричный метод (GOF): определите интерфейс для создания объекта, но позвольте подклассам решать, какой класс создать. Заводской метод позволяет классу отложить создание экземпляра до подклассов.

Абстрактная фабрика (GOF): предоставляет интерфейс для создания семейств связанных или зависимых объектов без указания их конкретных классов.

Источник путаницы: часто можно назвать класс, который используется в шаблоне "Factory Method", как "Factory". Этот класс абстрактный по определению. Поэтому этот класс легко назвать "Абстрактная фабрика". Но это просто название класса; не путайте его с шаблоном "Абстрактная фабрика" (имя класса!= имя шаблона). Паттерн "Абстрактная фабрика" отличается - он не использует абстрактный класс; он определяет интерфейс (не обязательно интерфейс языка программирования) для создания частей более крупного объекта или объектов, которые связаны друг с другом или должны быть созданы определенным образом.

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

Фабричный шаблон создает объекты на основе входных критериев, таким образом гарантируя, что вам не нужно писать код, как если бы он создавал этот объект, а не этот объект. Хорошим примером этого является туристический сайт. Веб-сайт о путешествиях может предоставить только информацию о поездке (рейс, поезд, автобус) или / и предоставить отели или / и предоставить туристические пакеты. Теперь, когда пользователь выбирает следующее, веб-сайт должен решить, какие объекты ему нужно создать. Должен ли он только создать объект путешествия или отель тоже.

Теперь, если вы планируете добавить еще один веб-сайт в свой портфель и считаете, что будет использоваться то же ядро, например, веб-сайт, объединяющий автомобили, который теперь ищет такси и осуществляет платежи в Интернете, вы можете использовать абстрактную фабрику в своем ядре. Таким образом, вы можете просто подключить еще одну фабрику кабин и карпов.

Обе фабрики не имеют ничего общего друг с другом, так что это хороший дизайн, чтобы держать их на разных фабриках.

Надеюсь, теперь это ясно. Еще раз изучите веб-сайт, помня об этом примере, надеюсь, он поможет. И я действительно надеюсь, что я правильно представил шаблоны:).

Никто не цитировал оригинальную книгу " Шаблоны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования", в которой дан ответ в первых двух абзацах раздела "Обсуждение шаблонов создания" (выделено мной):

Существует два распространенных способа параметризации системы с помощью классов создаваемых ею объектов. Один из способов - создать подкласс класса, который создает объекты; это соответствует использованию шаблона Factory Method (107). Главный недостаток этого подхода заключается в том, что может потребоваться новый подкласс только для изменения класса продукта. Такие изменения могут иметь каскадный характер. Например, когда создатель продукта сам создается фабричным методом, вы также должны переопределить его создателя.

Другой способ параметризации системы больше зависит от композиции объекта: определить объект, который отвечает за знание класса объектов продукта, и сделать его параметром системы. Это ключевой аспект паттернов Абстрактная фабрика (87), Строитель (97) и Прототип (117). Все три включают создание нового "фабричного объекта", в обязанности которого входит создание объектов продукта. Абстрактная фабрика имеет объект фабрики, производящий объекты нескольких классов. У Builder есть объект-фабрика, создающий сложный продукт постепенно, используя соответственно сложный протокол. У прототипа есть объект-фабрика, создающий продукт путем копирования объекта-прототипа. В этом случае фабричный объект и прототип являются одним и тем же объектом, потому что прототип отвечает за возврат продукта.

AbstractProductA, A1 and A2 both implementing the AbstractProductA
AbstractProductB, B1 and B2 both implementing the AbstractProductB

interface Factory {
    AbstractProductA getProductA(); //Factory Method - generate A1/A2
}

Используя Factory Method, пользователь может создать А1 или А2 AbstractProductA.

interface AbstractFactory {
    AbstractProductA getProductA(); //Factory Method
    AbstractProductB getProductB(); //Factory Method
}

Но у Abstract Factory, имеющей более 1 фабричного метода (например: 2 фабричных метода), используя эти фабричные методы, он создаст набор объектов / связанных объектов. Используя Abstract Factory, пользователь может создавать объекты A1, B1 объектов AbstractProductA, AbstractProductB.

Фабрика : или простая фабрика, класс, предназначенный для производства объектов определенного типа. Вызывающий будет вызывать это, чтобы получить определенный тип элемента. Например, SimplePizzaFactory будет производить простую пиццу с помощью SimplePizzaFactory.getPizza().

Фабричный метод Фабричный метод — это простой статический метод, который выдает запрошенный объект, этот фабричный метод обычно дает вызывающей стороне выбор того типа, который он хочет. например, PizzaFactory.getPizza(тип String), этот тип может быть «классическим», «фермерским», «маргаритой» или «специальным для страны».

Абстрактная фабрика Это просто фабрика фабрик, поэтому, если вы вызовете абстрактную фабрику, она даст вам объект фабрики. например, AbstractPizzaFactory даст вам VegPizzaFactory или NonVegPizzaFactory, а затем, используя VegPizzaFactory, вы сможете получить «фермерскую» пиццу.

Ссылочная статья https://www.binpress.com/factory-design-pattern/

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

Простой для понимания пример возникает при рассмотрении следующей ситуации.

У вас есть система, которая взаимодействует с другой системой. Я буду использовать пример, приведенный в «Шаблонах проектирования, объясненных Шеллоуэем и Троттом», стр. 194, потому что эта ситуация настолько редка, что я не могу придумать лучшего. В своей книге они приводят пример различных комбинаций локальных аппаратных ресурсов. В качестве примеров они используют:

  • Система с дисплеем высокого разрешения и драйвером печати
  • Система с дисплеем низкого разрешения и драйвером печати

Есть 2 варианта для одной переменной (драйвер печати, драйвер дисплея) и 2 опции для другой переменной (высокое разрешение, низкое разрешение). Мы хотим соединить их вместе таким образом, чтобы у насикоторые производят для нас как драйвер печати, так и драйвер дисплея правильного типа.

Это и есть шаблон абстрактной фабрики:

      class ResourceFactory
{
    virtual AbstractPrintDriver getPrintDriver() = 0;

    virtual AbstractDisplayDriver getDisplayDriver() = 0;
};

class LowResFactory : public ResourceFactory
{
    AbstractPrintDriver getPrintDriver() override
    {
        return LowResPrintDriver;
    }

    AbstractDisplayDriver getDisplayDriver() override
    {
        return LowResDisplayDriver;
    }
};

class HighResFactory : public ResourceFactory
{
    AbstractPrintDriver getPrintDriver() override
    {
        return HighResPrintDriver;
    }

    AbstractDisplayDriver getDisplayDriver() override
    {
        return HighResDisplayDriver;
    }
};

Я не буду подробно описывать иерархии драйверов печати и драйверов дисплея, для демонстрации будет достаточно одного.

      class AbstractDisplayDriver
{
    virtual void draw() = 0;
};

class HighResDisplayDriver : public AbstractDisplayDriver
{
    void draw() override
    {
        // do hardware accelerated high res drawing
    }
};

class LowResDisplayDriver : public AbstractDisplayDriver
{
    void draw() override
    {
        // do software drawing, low resolution
    }
};

Почему это работает:

Мы могли бы решить эту проблему с кучейзаявления:

      const resource_type = LOW_RESOLUTION;

if(resource_type == LOW_RESOLUTION)
{
    drawLowResolution();
    printLowResolution();
}
else if(resource_type == HIGH_RESOLUTION)
{
    drawHighResolution();
    printHighResolution();
}

Вместо этого мы теперь можем сделать:

          auto factory = HighResFactory;
    auto printDriver = factory.getPrintDriver();
    printDriver.print();
    auto displayDriver = factory.getDisplayDriver();
    displayDriver.draw();

По сути, мы абстрагировали логику времени выполнения в v-таблицу для наших классов.

Мое мнение об этом шаблоне таково, что он на самом деле не очень полезен. Он соединяет вместе некоторые вещи, которые не должны быть соединены вместе. Смысл шаблонов проектирования обычно в том, чтобы уменьшить связанность, а не увеличить ее, поэтому в некотором смысле этот шаблон действительно является анти-шаблоном, но в некоторых контекстах он может быть полезен.

Если вы дойдете до стадии, когда серьезно обдумываете реализацию этого, вы можете рассмотреть некоторые альтернативные проекты. Возможно, вы могли бы написать фабрику, которая возвращает фабрики, которые сами возвращают нужные вам объекты. Я предполагаю, что это было бы более гибким и не имело бы тех же проблем с соединением.

Приложение: Пример из «Банды четырех» связан аналогичным образом. У них естьи. Затем они производят,и,соответственно. Это несколько устаревший текст, поэтому может быть трудно понять контекст. Я помню, как читал эту главу и мало что понял из примера, кроме двух реализаций фабричного базового класса, которые возвращают разные семейства объектов.

Другие вопросы по тегам