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

Ответы:

386

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

Таким образом, для меня фабричный образец похож на агентство по найму. У вас есть кто-то, кому потребуется переменное количество работников. Этот человек может знать некоторую информацию, которая ему нужна в людях, которых он нанимает, но это все.

Поэтому, когда им нужен новый сотрудник, они звонят в агентство по найму и говорят им, что им нужно. Теперь, чтобы нанять кого-то, вам нужно знать много вещей - льготы, подтверждение соответствия требованиям и т. Д. Но нанимающему персоналу не нужно ничего знать об этом - агентство по найму все это обрабатывает.

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

public interface IThingFactory
{
    Thing GetThing(string theString);
}

public class ThingFactory : IThingFactory
{
    public Thing GetThing(string theString)
    {
        return new Thing(theString, firstDependency, secondDependency);
    }
}

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

kyoryu
источник
17
Где конкретная реализация GetThing () получает значения firstDependency и secondDependency?
Mikeyg36
88
Может кто-нибудь сказать мне, как это отвечает на вопрос ОП? Это просто описывает, что такое «фабричный шаблон», а затем добавляет пример «фабричного метода», который является лишь одним из трех «фабричных шаблонов». Другими словами, я нигде не вижу сравнения.
аналитик
4
Вопрос ОП явно упоминается within an object instead of a Factory class. Я думаю, что он имел в виду сценарий, в котором вы делаете ctor приватным и используете статический метод для создания экземпляра класса (создания объекта). Но чтобы следовать этому примеру, нужно ThingFactoryсначала создать экземпляр класса, чтобы получить Thingобъекты, что делает его Factory classдействительным.
atiyar
4
Извините, но объяснение дерьмо, потому что конструктор также может быть написан таким образом, чтобы скрыть зависимости. Основная фоновая информация, которую вы хотите отделить от информации о создании зависимостей, отсутствует. Кроме того, вопрос был о том же классе, ответ никак не связан с этим.
Кристиан Худжер
8
ОП спросил когда . Киорю ответил как . Хотя стиль ответа заслуживает высокой оценки, в контексте этого вопроса это просто шум.
8bitjunkie
96

Фабричные методы следует рассматривать как альтернативу конструкторам - в основном, когда конструкторы недостаточно выразительны, т.е.

class Foo{
  public Foo(bool withBar);
}

не так выразительно, как:

class Foo{
  public static Foo withBar();
  public static Foo withoutBar();
}

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

Расмус Фабер
источник
2
Где здесь класс Фабрика?
Корай Тугай
20
@KorayTugay: нет фабричного класса, только фабричные методы. Вопрос в том, когда использовать фабричные методы вместо фабричного класса. Но фабричные методы являются скорее альтернативой конструкторам, чем альтернативой фабричным классам. (Я не знаю, почему верхний ответ оценивается так высоко, несмотря на то, что я говорю только о фабричных классах).
Расмус Фабер
5
Следует отметить, что статические фабричные методы полностью отличаются от шаблона проектирования Gang of Four: Factory Method.
jaco0646
76

Одна ситуация, когда я лично нахожу отдельные классы Фабрики, чтобы иметь смысл, - когда конечный объект, который вы пытаетесь создать, зависит от нескольких других объектов. Например, в PHP: предположим, у вас есть Houseобъект, который, в свою очередь, имеет объект a Kitchenи LivingRoomобъект, и у LivingRoomобъекта также есть TVобъект внутри.

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

Альтернатива состоит в том, чтобы сделать следующее (внедрение зависимости, если вам нравится причудливый термин):

$TVObj = new TV($param1, $param2, $param3);
$LivingroomObj = new LivingRoom($TVObj, $param1, $param2);
$KitchenroomObj = new Kitchen($param1, $param2);
$HouseObj = new House($LivingroomObj, $KitchenroomObj);

Здесь, если процесс создания Houseнеудачи есть, есть только одно место для поиска, но необходимость использовать этот кусок каждый раз, когда кто-то хочет нового House, далеко не удобна. Введите фабрики:

class HouseFactory {
    public function create() {
        $TVObj = new TV($param1, $param2, $param3);
        $LivingroomObj = new LivingRoom($TVObj, $param1, $param2);
        $KitchenroomObj = new Kitchen($param1, $param2);
        $HouseObj = new House($LivingroomObj, $KitchenroomObj);

        return $HouseObj;
    }
}

$houseFactory = new HouseFactory();
$HouseObj = $houseFactory->create();

Благодаря фабрике процесс создания a Houseабстрагируется (в том, что вам не нужно создавать и настраивать каждую отдельную зависимость, когда вы просто хотите создать a House) и в то же время централизован, что облегчает поддержку. Существуют и другие причины, по которым использование отдельных фабрик может быть полезным (например, тестируемость), но я нахожу этот конкретный пример использования, чтобы лучше всего показать, как классы Фабрики могут быть полезны.

Mahn
источник
1
Как бы кто-то сделал юнит-тест на это, хотя? Я думал, что использование «нового» ключевого слова в классе считается плохой практикой, потому что оно не может быть проверено модулем. Или фабрика должна быть чем-то вроде исключения из этого правила?
AgmLauncher
1
@AgmLauncher У меня тоже был такой же вопрос, когда я начинал с модульного тестирования, посмотрите: stackoverflow.com/questions/10128780/…
Mahn
1
Не понял этого. Как именно параметры для создания различных объектов передаются в HouseFactoryкласс?
atiyar
1
@Mahn, разве у тебя не получится много параметров?
Pacerier
1
@Pacerier - это то, что вы сами решаете, как моделировать, в зависимости от ваших потребностей, но вам не всегда нужно передавать каждый параметр в createметод. Например, если у вас Houseвсегда будет один и тот же тип, LivingRoomто может иметь смысл жестко закодировать его параметры в фабричном классе, а не в качестве аргументов. Или вы можете предоставить typeаргумент вашему HouseFactory::createметоду, если у вас есть несколько видов LivingRooms и есть переключатель внутри с параметрами, жестко закодированными для каждого типа.
Махн
19

Важно четко дифференцировать идею использования фабричного или фабричного метода. Оба предназначены для решения взаимоисключающих различного рода проблем создания объектов.

Давайте определимся с "фабричным методом":

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

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

interface Deliverable 
{
    /*********/
}

abstract class DefaultProducer 
{

    public void taskToBeDone() 
    {   
        Deliverable deliverable = factoryMethodPattern();
    }
    protected abstract Deliverable factoryMethodPattern();
}

class SpecificDeliverable implements Deliverable 
{
 /***SPECIFIC TASK CAN BE WRITTEN HERE***/
}

class SpecificProducer extends DefaultProducer 
{
    protected Deliverable factoryMethodPattern() 
    {
        return new SpecificDeliverable();
    }
}

public class MasterApplicationProgram 
{
    public static void main(String arg[]) 
    {
        DefaultProducer defaultProducer = new SpecificProducer();
        defaultProducer.taskToBeDone();
    }
}
Пракаш Чипа
источник
15

Они также полезны, когда вам нужно несколько «конструкторов» с одинаковым типом параметра, но с другим поведением.

Rik
источник
15

Рекомендуется использовать фабричные методы внутри объекта, когда:

  1. Класс объекта не знает, какие именно подклассы он должен создать
  2. Класс объекта разработан так, чтобы создаваемые им объекты были определены подклассами.
  3. Класс объекта делегирует свои обязанности вспомогательным подклассам и не знает, какой именно класс будет выполнять эти обязанности

Рекомендуется использовать абстрактный класс фабрики, когда:

  1. Ваш объект не должен зависеть от того, как его внутренние объекты созданы и спроектированы
  2. Группа связанных объектов должна использоваться вместе, и вам нужно выполнить это ограничение
  3. Объект должен быть настроен одним из нескольких возможных семейств связанных объектов, которые будут частью вашего родительского объекта.
  4. Требуется совместно использовать дочерние объекты, показывающие только интерфейсы, но не реализацию
Дзианис Яфимау
источник
9

UML от

введите описание изображения здесь

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

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

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

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

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

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

import java.util.HashMap;


/* Product interface as per UML diagram */
interface Game{
    /* createGame is a complex method, which executes a sequence of game steps */
    public void createGame();
}

/* ConcreteProduct implementation as per UML diagram */
class Chess implements Game{
    public Chess(){

    }
    public void createGame(){
        System.out.println("---------------------------------------");
        System.out.println("Create Chess game");
        System.out.println("Opponents:2");
        System.out.println("Define 64 blocks");
        System.out.println("Place 16 pieces for White opponent");
        System.out.println("Place 16 pieces for Black opponent");
        System.out.println("Start Chess game");
        System.out.println("---------------------------------------");
    }
}
class Checkers implements Game{
    public Checkers(){

    }
    public void createGame(){
        System.out.println("---------------------------------------");
        System.out.println("Create Checkers game");
        System.out.println("Opponents:2 or 3 or 4 or 6");
        System.out.println("For each opponent, place 10 coins");
        System.out.println("Start Checkers game");
        System.out.println("---------------------------------------");
    }
}
class Ludo implements Game{
    public Ludo(){

    }
    public void createGame(){
        System.out.println("---------------------------------------");
        System.out.println("Create Ludo game");
        System.out.println("Opponents:2 or 3 or 4");
        System.out.println("For each opponent, place 4 coins");
        System.out.println("Create two dices with numbers from 1-6");
        System.out.println("Start Ludo game");
        System.out.println("---------------------------------------");
    }
}

/* Creator interface as per UML diagram */
interface IGameFactory {
    public Game getGame(String gameName);
}

/* ConcreteCreator implementation as per UML diagram */
class GameFactory implements IGameFactory {

     HashMap<String,Game> games = new HashMap<String,Game>();
    /*  
        Since Game Creation is complex process, we don't want to create game using new operator every time.
        Instead we create Game only once and store it in Factory. When client request a specific game, 
        Game object is returned from Factory instead of creating new Game on the fly, which is time consuming
    */

    public GameFactory(){

        games.put(Chess.class.getName(),new Chess());
        games.put(Checkers.class.getName(),new Checkers());
        games.put(Ludo.class.getName(),new Ludo());        
    }
    public Game getGame(String gameName){
        return games.get(gameName);
    }
}

public class NonStaticFactoryDemo{
    public static void main(String args[]){
        if ( args.length < 1){
            System.out.println("Usage: java FactoryDemo gameName");
            return;
        }

        GameFactory factory = new GameFactory();
        Game game = factory.getGame(args[0]);
        if ( game != null ){                    
            game.createGame();
            System.out.println("Game="+game.getClass().getName());
        }else{
            System.out.println(args[0]+  " Game does not exists in factory");
        }           
    }
}

вывод:

java NonStaticFactoryDemo Chess
---------------------------------------
Create Chess game
Opponents:2
Define 64 blocks
Place 16 pieces for White opponent
Place 16 pieces for Black opponent
Start Chess game
---------------------------------------
Game=Chess

В этом примере показан Factoryкласс путем реализации FactoryMethod.

  1. Gameэто интерфейс для всех типов игр. Он определяет сложный метод:createGame()

  2. Chess, Ludo, Checkers различные варианты игр, которые обеспечивают реализацию createGame()

  3. public Game getGame(String gameName)находится FactoryMethodв IGameFactoryклассе

  4. GameFactoryпредварительно создает другой тип игр в конструкторе. Он реализует IGameFactoryфабричный метод.

  5. Название игры передается в качестве аргумента командной строки NotStaticFactoryDemo

  6. getGamein GameFactoryпринимает имя игры и возвращает соответствующий Gameобъект.

Фабрика:

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

FactoryMethod

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

Случай использования:

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

Равиндра Бабу
источник
спасибо вам за раздел с ключевыми словами, это лаконично для меня. но, это невообразимо, что «getArea () является фабричным методом в интерфейсе Shape», потому что метод getArea НИКОГДА не создает экземпляр какого-либо класса, он просто выполняет вычисление .review, которое «определяет интерфейс для создания объекта, но пусть подклассы решают, какой класс создавать экземпляры "пожалуйста.
Реко
getArea()это не фабричный метод вообще .
Cranio
1
У меня другое мнение - эксперты, пожалуйста, подтвердите и поставьте заметки. 1. Клиенту (или вызывающему) нужен объект интереса ... следовательно, он не должен вызывать новый GameFactory (), а класс Factory должен иметь статический getInstance () 2. Также если так, то games.put (Chess.class.getName ( ), новые шахматы ()); всегда будет возвращать одну и ту же ссылку на шахматы [если реализовано как статическое] - как обработать этот сценарий наиболее эффективным способом?
Арнаб Датта
Я привел пример нестатической Фабрики. Вы можете реализовать его с помощью статических блоков и методов, если хотите. По поводу ваших запросов: 1. Клиент позвонит на Фабрику, чтобы получить Игру. 2. Я помещаю объект один раз, и все Get возвращают один и тот же экземпляр - один и тот же referenc.e из Chess возвращается для каждого get
Равиндра Бабу
6

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

Брэд Уилсон
источник
4

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

WCF использует классы ServiceHostFactory для извлечения объектов ServiceHost в различных ситуациях. Стандартный ServiceHostFactory используется IIS для извлечения экземпляров ServiceHost для файлов .svc , но WebScriptServiceHostFactory используется для служб, которые возвращают сериализации клиентам JavaScript. ADO.NET Data Services имеет свой собственный специальный DataServiceHostFactory, а ASP.NET имеет свой ApplicationServicesHostFactory, так как его сервисы имеют частные конструкторы.

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

Марк Сидаде
источник
2

Рассмотрим сценарий, когда вам нужно разработать класс Order и Customer. Для простоты и начальных требований вы не чувствуете необходимости в фабрике для класса Order и заполняете вашу заявку множеством операторов 'new Order ()'. Все работает хорошо.

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

1. Вы создаете перегрузку конструктора, которая будет работать только для новых реализаций. (Неприемлемо). 2- Вы изменяете подписи Order () и меняете каждый вызов. (Не хорошая практика и настоящая боль).

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

Мухаммед Авайс
источник
1

если вы хотите создать другой объект с точки зрения использования. Это полезно.

public class factoryMethodPattern {
      static String planName = "COMMERCIALPLAN";
      static int units = 3;
      public static void main(String args[]) {
          GetPlanFactory planFactory = new GetPlanFactory();
          Plan p = planFactory.getPlan(planName);
          System.out.print("Bill amount for " + planName + " of  " + units
                        + " units is: ");
          p.getRate();
          p.calculateBill(units);
      }
}

abstract class Plan {
      protected double rate;

      abstract void getRate();

      public void calculateBill(int units) {
            System.out.println(units * rate);
      }
}

class DomesticPlan extends Plan {
      // @override
      public void getRate() {
            rate = 3.50;
      }
}

class CommercialPlan extends Plan {
      // @override
      public void getRate() {
            rate = 7.50;
      }
}

class InstitutionalPlan extends Plan {
      // @override
      public void getRate() {
            rate = 5.50;
      }
}

class GetPlanFactory {

      // use getPlan method to get object of type Plan
      public Plan getPlan(String planType) {
            if (planType == null) {
                  return null;
            }
            if (planType.equalsIgnoreCase("DOMESTICPLAN")) {
                  return new DomesticPlan();
            } else if (planType.equalsIgnoreCase("COMMERCIALPLAN")) {
                  return new CommercialPlan();
            } else if (planType.equalsIgnoreCase("INSTITUTIONALPLAN")) {
                  return new InstitutionalPlan();
            }
            return null;
      }
}
Самет ОЗТОПРАК
источник
1

Любой класс, откладывающий создание объекта до его подкласса для объекта, с которым он должен работать, можно рассматривать как пример шаблона Factory.

Я подробно упомянул в другом ответе на https://stackoverflow.com/a/49110001/504133

nits.kk
источник
1

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

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

Другими словами, проще изменить вещи, если вы используете фабричный метод, чем если вы используете простую фабрику (известную как фабричный класс).

Посмотрите на этот пример: https://connected2know.com/programming/java-factory-pattern/ . Теперь представьте, что вы хотите принести новое животное. В классе Factory вам нужно изменить Factory, но в методе factory нет, вам нужно только добавить новый подкласс.

Тежу
источник
0

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

Я нашел этот полезный шаблон, особенно когда я не могу заменить или неадекватно ORM и хочу эффективно создавать экземпляры многих объектов из объединений таблиц БД или хранимых процедур.

jonfm
источник
0

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

Заводской петтер использует полиморфизм, инъекцию зависимостей и инверсию контроля.

Заявленная цель фабричных шаблонов: Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

Итак, допустим, что вы создаете операционную систему или платформу и строите все отдельные компоненты.

Вот простой пример концепции Factory Pattern в PHP. Я не могу быть 100% на всем этом, но это предназначено, чтобы служить простым примером. Я не эксперт.

class NumbersFactory {
    public static function makeNumber( $type, $number ) {
        $numObject = null;
        $number = null;

        switch( $type ) {
            case 'float':
                $numObject = new Float( $number );
                break;
            case 'integer':
                $numObject = new Integer( $number );
                break;
            case 'short':
                $numObject = new Short( $number );
                break;
            case 'double':
                $numObject = new Double( $number );
                break;
            case 'long':
                $numObject = new Long( $number );
                break;
            default:
                $numObject = new Integer( $number );
                break;
        }

        return $numObject;
    }
}

/* Numbers interface */
abstract class Number {
    protected $number;

    public function __construct( $number ) {
        $this->number = $number;
    }

    abstract public function add();
    abstract public function subtract();
    abstract public function multiply();
    abstract public function divide();
}
/* Float Implementation */
class Float extends Number {
    public function add() {
        // implementation goes here
    }

    public function subtract() {
        // implementation goes here
    }

    public function multiply() {
        // implementation goes here
    }

    public function divide() {
        // implementation goes here
    }
}
/* Integer Implementation */
class Integer extends Number {
    public function add() {
        // implementation goes here
    }

    public function subtract() {
        // implementation goes here
    }

    public function multiply() {
        // implementation goes here
    }

    public function divide() {
        // implementation goes here
    }
}
/* Short Implementation */
class Short extends Number {
    public function add() {
        // implementation goes here
    }

    public function subtract() {
        // implementation goes here
    }

    public function multiply() {
        // implementation goes here
    }

    public function divide() {
        // implementation goes here
    }
}
/* Double Implementation */
class Double extends Number {
    public function add() {
        // implementation goes here
    }

    public function subtract() {
        // implementation goes here
    }

    public function multiply() {
        // implementation goes here
    }

    public function divide() {
        // implementation goes here
    }
}
/* Long Implementation */
class Long extends Number {
    public function add() {
        // implementation goes here
    }

    public function subtract() {
        // implementation goes here
    }

    public function multiply() {
        // implementation goes here
    }

    public function divide() {
        // implementation goes here
    }
}

$number = NumbersFactory::makeNumber( 'float', 12.5 );
Роберт Роша
источник
Я понимаю, что здесь происходит, но я не понимаю, в чем суть. Что NumbersFactory::makeNumber( 'float', 12.5 );дает мне просто сказать, new Float(12.5);если я знаю, что мне нужно Float? Это то, что я не понимаю о фабриках ... какой смысл?
BadHorsie
Он позволяет вам выбирать разные реализации и не привязывает вас только к одной. Интерфейс установлен, и все реализации должны гарантировать и соблюдать его.
Роберт Роча