Фабричний візерунок. Коли використовувати заводські методи?


Відповіді:


386

Мені подобається думати про дизайнерські паттенти з точки зору того, що мої заняття є "людьми".

Отже, для мене заводський зразок - це як агентство найму. У вас є хтось, кому знадобиться змінна кількість працівників. Ця людина може знати інформацію, яка їм потрібна, у людей, яких вони наймають, але це все.

Тож, коли їм потрібен новий працівник, вони дзвонять в наймане агентство і кажуть їм, що їм потрібно. Тепер, щоб насправді когось найняти , потрібно знати багато речей - переваги, перевірку відповідності тощо. Але особа, яка наймає, не повинна нічого з цього знати - агенція найму займається цим усім.

Таким же чином, використання Фабрики дозволяє споживачеві створювати нові об'єкти, не знаючи деталей того, як вони створені, або які їх залежності - вони повинні лише дати інформацію, яку вони насправді хочуть.

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

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

Отже, тепер споживач ThingFactory може отримати річ, не знаючи про залежність речі, за винятком рядкових даних, що надходять від споживача.


17
Де конкретна реалізація GetThing () отримує значення firstDependency і secondDependpend?
Mikeyg36

88
Може хтось скаже мені, як це відповідає на питання ОП? Це просто описує, що таке «Заводський шаблон», а потім додає приклад «Фабричного методу», який є лише однією з трьох «Фабричних моделей». Іншим словом, я ніде не бачу порівняння.
Прогноз

4
Питання ОП чітко згадується within an object instead of a Factory class. Я думаю, що він мав на увазі сценарій, коли ви робите ctor приватним і використовуєте статичний метод для екземпляра класу (створення об'єкта). Але, щоб наслідувати цей приклад, потрібно ThingFactoryспочатку інстанціювати клас, щоб отримати Thingоб’єкти, що робить це Factory classфактично.
атіяр

4
Вибачте, але пояснення це лайно, тому що конструктор також може бути записаний таким чином, щоб приховати залежності. Основна довідкова інформація про те, що ви хочете відокремити інформацію про створення залежності від управління залежністю, відсутня. Крім того, питання стосувалося одного класу, відповідь жодним чином не пов'язаний з цим.
Крістіан Худжер

8
ОП запитав, коли . Кирю відповів як . Хоча стиль відповіді похвальний, у контексті цього питання це лише шум.
8bitjunkie

96

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

class Foo{
  public Foo(bool withBar);
}

не так виразно, як:

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

Фабричні класи корисні, коли вам потрібен складний процес побудови об'єкта, коли для будівництва потрібна залежність, яку ви не хочете для фактичного класу, коли вам потрібно побудувати різні об'єкти тощо.


2
Де тут клас Фабрика?
Корай Тугай

20
@KorayTugay: Фабричного класу немає, лише фабричні методи. Питання полягає в тому, коли використовувати заводські методи замість фабричного класу. Але фабричні методи - це більше альтернатива конструкторам, ніж альтернатива фабричним класам. (Я не знаю, чому найвища відповідь оцінена настільки високо, незважаючи тільки на розмови про фабричні класи).
Расмус Фабер

5
Слід зазначити, що статичні заводські методи абсолютно відрізняються від групи «Банда четвірок»: «Заводський метод дизайну».
jaco0646

76

Одна з ситуацій, коли я особисто знаходжу окремі класи Фабрики, щоб мати сенс, коли кінцевий об'єкт, який ви намагаєтесь створити, спирається на кілька інших об'єктів. Наприклад, в PHP: Припустимо, у вас є Houseоб'єкт, який, у свою чергу, має 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();

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


1
Як би хтось зробив одиничне тестування на цьому? Я вважав, що використання «нового» ключового слова в класі вважалося поганою практикою, оскільки його не можна перевірити одиницею. Або фабрика мала бути винятком із цього правила?
AgmLauncher

1
@AgmLauncher У мене був один і той же питання, а коли я почав з модульного тестування, перевірте: stackoverflow.com/questions/10128780 / ...
Mahn

1
Не отримав цього. Як саме парами для створення різних об’єктів передаються HouseFactoryкласу?
атіяр

1
@Mahn, чи не закінчилося б у вас багато параметів?
Pacerier

1
@Pacerier - це щось для вас, щоб вирішити, як моделювати, залежно від ваших потреб, але вам не завжди потрібно передавати кожен параметр createметоду. Наприклад, якщо у вас Houseзавжди буде однаковий вид, LivingRoomтоді може бути сенс, щоб його парами були твердо кодованими у заводському класі, а не передавались як аргументи. Або ви можете надати typeаргумент своєму HouseFactory::createметоду, якщо у вас є кілька типів LivingRooms і у вас є комутатор всередині з параметрами, жорстко кодованими для кожного типу.
Ман

19

Важливо чітко розмежувати ідею, використовуючи заводський чи заводський метод. Обидва призначені для вирішення взаємовиключних проблем різного типу об'єктів.

Давайте конкретизуємо "заводський метод":

Перше, що ви розробляєте бібліотеку чи API, які в свою чергу будуть використовуватися для подальшої розробки додатків, то заводський метод є одним з найкращих виборів для створення структури. Причина позаду; Ми знаємо, що коли створити об’єкт необхідної функціональності, але тип об'єкта залишиться невизначеним, або буде прийнято рішення щодо передачі динамічних параметрів .

Тепер справа полягає в тому, що приблизно такого ж можна досягти, використовуючи сам заводський зразок, але один величезний недолік внесе в систему, якщо заводський зразок буде використаний для висвітленої проблеми, це те, що ваша логіка класифікації різних об'єктів (об'єктів підкласів) буде бути специфічним для певного стану бізнесу, тому в майбутньому, коли вам потрібно розширити функціональність вашої бібліотеки на інші платформи (технічніше, вам потрібно додати більше підкласів базового інтерфейсу або абстрактного класу, щоб завод повертав ці об'єкти також на додаток до існуючих виходячи з деяких динамічних параметрів), то кожен раз, коли вам потрібно буде змінювати (розширювати) логіку заводського класу, що буде дорого працювати і не добре з точки зору дизайну. З іншого боку, якщо "заводський метод"

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

Вони також корисні, коли вам потрібно кілька "конструкторів" з одним типом параметрів, але з різною поведінкою.


15

Добре використовувати фабричні методи всередині об'єкта, коли:

  1. Клас об'єкта не знає, які саме підкласи він повинен створити
  2. Клас об'єкта створений так, що об'єкти, які він створює, були визначені підкласами
  3. Клас об'єкта делегує свої обов'язки допоміжним підкласам і не знає, який саме клас прийме ці обов'язки

Добре використовувати абстрактний заводський клас, коли:

  1. Ваш об'єкт не повинен залежати від того, як створюються та проектуються його внутрішні об'єкти
  2. Групу зв'язаних об'єктів слід використовувати разом, і вам потрібно виконувати це обмеження
  3. Об'єкт повинен бути налаштований однією з кількох можливих родин пов'язаних об'єктів, які будуть частиною вашого батьківського об'єкта
  4. Потрібно ділитися дочірніми об’єктами, що показують лише інтерфейси, але не реалізацію

9

UML від

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

Продукт: Він визначає інтерфейс об'єктів, створених заводським методом.

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

Творець: оголошує заводський метод

ConcreateCreator: реалізує заводський метод для повернення екземпляра 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клас, застосовуючи a FactoryMethod.

  1. Game- це інтерфейс для всіх типів ігор. Він визначає складний метод:createGame()

  2. Chess, Ludo, Checkers - це різні варіанти ігор, які забезпечують реалізацію createGame()

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

  4. GameFactoryпопередньо створює різні ігри в конструкторі. Він реалізує IGameFactoryзаводський метод.

  5. Ім'я гри передається як аргумент командного рядка NotStaticFactoryDemo

  6. getGameу програмі GameFactoryприймає назву гри та повертає відповідний Gameоб’єкт.

Фабрика:

Створює об'єкти, не піддаючи клієнту логіку інстанції.

FactoryMethod

Визначте інтерфейс для створення об'єкта, але нехай підкласи вирішують, який клас інстанціювати. Фабричний метод дозволяє відкладати екземпляр класу для підкласів

Корпус:

Коли використовувати: Clientне знає, які конкретні класи потрібно буде створити під час виконання, але просто хоче отримати клас, який зробить цю роботу.


дякую за вас, розділ викладок, для мене це стисло. інтерфейс для створення об'єкта, але нехай підкласи вирішують, який клас для інстанції "будь ласка.
відновлення

getArea()НЕ фабричний метод взагалі .
Краніо

1
У мене інша думка - експерти прошу підтвердити та робити примітки. 1. Клієнту (або заявнику) потрібен Об'єкт інтересу ... отже, не потрібно викликати новий GameFactory (), а заводський клас повинен мати статичний getInstance () 2. Так само якщо так, то games.put (Chess.class.getName ( ), нові шахи ()); завжди буде повертати одне і те ж посилання на Шахи [якщо реалізовано як статичне] - як впоратися з цим сценарієм найефективніше?
Арнаб Дутта

Я наводив приклад нестатичної Фабрики. Ви можете реалізувати його за допомогою статичних блоків і методів, якщо хочете. Щодо ваших запитів: 1. Клієнт зателефонує заводу, щоб отримати гру. 2. Я кладу об’єкт один раз, і все Get поверне той самий екземпляр - той самий referenc.e шахів повертається за кожне отримання
Равіндра, бабу,

6

Це справді справа смаку. Заводські класи можуть бути абстраговані / пов’язані між собою за необхідності, тоді як фабричні методи мають меншу вагу (а також мають тенденцію до перевірки, оскільки вони не мають визначеного типу, але для них потрібен відомий пункт реєстрації, схожий на службу локатор, але для визначення заводських методів).


4

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

WCF використовує класи ServiceHostFactory для отримання об’єктів ServiceHost в різних ситуаціях. Стандартний ServiceHostFactory використовується IIS для отримання екземплярів ServiceHost для файлів .svc , але WebScriptServiceHostFactory використовується для сервісів, які повертають серіалізації клієнтам JavaScript. Служби даних ADO.NET мають свої спеціальні DataServiceHostFactory, а ASP.NET має свою ApplicationServicesHostFactory, оскільки в її службах є приватні конструктори.

Якщо у вас є лише один клас, який споживає фабрику, то ви можете просто використовувати заводський метод у цьому класі.


2

Розглянемо сценарій, коли потрібно розробити замовлення та клас клієнтів. Для простоти та початкових вимог ви не відчуваєте потреби в заводських класах для замовлення та наповнюєте свою заявку багатьма заявами "new 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

Будь-який клас, який визначає створення об'єкта його підкласу для об'єкта, з яким він повинен працювати, може розглядатися як приклад заводського шаблону.

Я детально згадав в іншій відповіді за адресою https://stackoverflow.com/a/49110001/504133


1

Я думаю, це буде залежати від ступеня зв'язаного зв'язку, яку ви хочете внести до свого коду.

Фабричний метод дуже добре роз’єднує речі, але заводський клас немає.

Іншими словами, змінити речі простіше, якщо ви використовуєте заводський метод, ніж якщо ви використовуєте просту фабрику (відомий як заводський клас).

Подивіться на цей приклад: https://connected2know.com/programming/java-factory-pattern/ . А тепер уявіть, що ви хочете привезти нову Тварину. У класі Factory потрібно змінити Factory, але у заводському методі - ні, вам потрібно лише додати новий підклас.


0

Заводські класи більш важкі, але дають певні переваги. У випадках, коли вам потрібно створити об'єкти з декількох необроблених джерел даних, вони дозволяють вам інкапсулювати лише логіку побудови (а може бути і агрегацію даних) в одному місці. Там його можна перевірити абстрактно, не торкаючись об’єктного інтерфейсу.

Я вважаю це корисною схемою, особливо там, де я не в змозі замінити і неадекватний ORM і хочу ефективно інстанціювати багато об'єктів з таблиці приєднань та збережених процедур.


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

Це дозволяє обирати різні реалізації та не пов'язує вас лише з одним. Інтерфейс встановлений, і всі реалізації повинні гарантувати його та шанувати.
Роберт Роша
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.