Шаблони, що породжують: Фабричний метод (Factory Method). Паттерн Фабричний метод (Factory Method) — рівень класу Паттерн фабричний метод

Останнє оновлення: 31.10.2015

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

Коли треба застосовувати патерн

    Коли невідомо, об'єкти яких типів необхідно створювати

    Коли система має бути незалежною від процесу створення нових об'єктів та розширюваною: до неї можна легко вводити нові класи, об'єкти яких система має створювати.

    Коли створення нових об'єктів необхідно делегувати із базового класу класам спадкоємцям

На мові UML патерн можна описати так:

Формальне визначення патерну мовою C# може виглядати так:

Abstract class Product () class ConcreteProductA: Product () class ConcreteProductB: Product () abstract class Creator ( public abstract Product FactoryMethod(); ) class ConcreteCreatorA: Creator ( public override Product FactoryMethod() ( return new ConcreteProductA(); ConcreteCreatorB: Creator ( public override Product FactoryMethod() ( return new ConcreteProductB(); ) )

Учасники

    Абстрактний клас Product визначає інтерфейс класу, об'єкти якого потрібно створювати.

    Конкретні класи ConcreteProductA та ConcreteProductB представляють реалізацію класу Product. Таких класів може бути безліч

    Абстрактний клас Creator визначає абстрактний фабричний метод FactoryMethod(), який повертає об'єкт Product.

    Конкретні класи ConcreteCreatorA та ConcreteCreatorB - спадкоємці класу Creator, що визначають свою реалізацію методу FactoryMethod(). Причому метод FactoryMethod() кожного окремого класу-творця повертає певний тип продукту. До кожного конкретного класу продукту визначається свій конкретний клас творця.

    Таким чином, клас Creator делегує створення об'єкта Product своїм спадкоємцям. А класи ConcreteCreatorA і ConcreteCreatorB можуть самостійно вибирати якийсь конкретний тип продукту їм створювати.

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

Class Program ( static void Main(string args) ( Developer dev = new PanelDeveloper("ТОВ ЦеглаБуд"); House house2 = dev.Create(); dev = new WoodDeveloper("Приватний забудовник"); House house = dev.Create( ); Console.ReadLine(); ) ) // абстрактний клас будівельної компанії Abstract class Developer ( public string Name ( get; set; ) ); ) // будує панельні будинки class PanelDeveloper: Developer ( public PanelDeveloper(string n) : base(n) ( ) public override House Create() ( return new PanelHouse(); ) ) // будує дерев'яні будинки class WoodDeveloper: Developer ( public WoodDeveloper(string n) : base(n) ( ) public override House Create() ( return new WoodHouse(); ) ) abstract class House ( ) class PanelHouse: House ( public PanelHouse() ( Console.WriteLine("Панельний будинок побудований"); ) ) class WoodHouse: House ( public WoodHouse() ( Console.WriteLine("Дерев'яний будинок побудований");

)

Як абстрактний клас Product тут виступає клас House. Його дві конкретні продажу - PanelHouse і WoodHouse представляють типи будинків, які будуватимуть підрядники. Як абстрактний клас творця виступає Developer, що визначає абстрактний метод Create() . Цей метод реалізується у класах-спадкоємцях WoodDeveloper та PanelDeveloper. І якщо в майбутньому нам потрібно буде побудувати будинки якогось іншого типу, наприклад цегляні, то ми можемо з легкістю створити новий клас цегляних будинків, успадкований від House, і визначити клас відповідного підрядника. Таким чином, система вийде легко розширюється. Щоправда, недоліки патерну теж очевидні - кожному за нового продукту необхідно створювати свій клас творця.

Паттерн Абстрактна фабрика (Abstract Factory) – рівень об'єкта

Назва та класифікація патерну

Абстрактна фабрика - патерн, що породжує об'єкти.

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

Застосовність

Використання патерну Abstract Factory (абстрактна фабрика) доцільно, якщо:

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

Наведемо приклади груп взаємозалежних об'єктів.

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

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

І останній приклад. Вище йшлося про гру-стратегію «Пунічні війни». Очевидно, що зовнішній вигляд, бойові порядки та характеристики для різних родів військ (піхота, лучники, кіннота) у кожній армії будуть своїми. У цьому випадку сімейством взаємопов'язаних об'єктів будуть усі види воїнів для тієї чи іншої протиборчої сторони, при цьому має виключатися, наприклад, така ситуація, коли римська кіннота воює на боці Карфагену.

Структура

Структура паттерна Анотація фабрика показано на рис. 35.

Мал. 35.

Учасники

AbstractFactory - абстрактна фабрика: повідомляє інтерфейс для операцій, що створюють абстрактні об'єкти-продукти.

ConcreteFactory (ConcreteFactoryl, ConcreteFactory2) - конкретна фабрика: реалізує операції, що створюють конкретні об'єкти-продукти (для гри «Пунічні війни» створюються армії Риму та Карфагену).

AbstractProduct (Abstract Product A, Abstract Product B) - абстрактний продукт: оголошує інтерфейс для типу об'єкта-продукту.

ConcreteProduct (ProductA, Product B) - конкретний продукт: визначає об'єкт-продукт, який створюється відповідною конкретною фабрикою (наприклад, лучник, вершник), - реалізує інтерфейс Abstract Product.

Client – ​​клієнт: користується виключно інтерфейсами, які оголошені у класах AbstractFactory та AbstractProduct.

Відносини

Зазвичай під час виконання створюється єдиний екземпляр класу ConcreteFactory. Ця конкретна фабрика створює об'єкти-продукти, що мають цілком певну реалізацію. Для створення інших видів об'єктів клієнт має скористатися іншою конкретною фабрикою.

Abstract Factory перевіряє створення об'єктів-продуктів своєму підкласу ConcreteFactory.

Результати

Паттерн Абстрактна фабрика має наступні плюси та мінуси:

  • ізолює конкретні класи.Допомагає контролювати класи об'єктів, створюваних програмою. Оскільки фабрика інкапсулює відповідальність за створення класів і сам процес їх створення, вона ізолює клієнта від деталей реалізації класів. Клієнти маніпулюють екземплярами через їх абстрактні інтерфейси. Імена класів, що виготовляються, відомі тільки конкретній фабриці, в коді клієнта вони не згадуються;
  • полегшує заміну сімейств товарів.Клас конкретної фабрики з'являється у додатку лише один раз: при інстанціюванні. Це полегшує заміну конкретної фабрики, що використовується додатком. Програма може змінити конфігурацію продуктів, просто підставивши нову конкретну фабрику. Оскільки абстрактна фабрика створює все сімейство продуктів, те й замінюється відразу всієї родини. У нашому прикладі інтерфейсу користувача перейти від віджетів Motif до віджетів Presentation Manager можна, просто переключившись на продукти відповідної фабрики і заново створивши інтерфейс;
  • гарантує поєднання продуктів.Якщо продукти певної родини спроектовані для спільного використання, то важливо, щоб програма в кожний момент часу працювала лише з продуктами єдиної родини. Клас Abstract Factory дозволяє легко дотриматися цього обмеження;
  • підтримати новий вид продуктів важко.Розширення абстрактної фабрики виготовлення нових видів продуктів - непросте завдання. Інтерфейс Abstract Factory фіксує набір продуктів, які можна створити. Для підтримки нових продуктів необхідно розширити інтерфейс фабрики, тобто змінити клас AbstractFactory та всі його підкласи.

Приклад коду для патерну Abstract Factory

Наведемо реалізацію патерну Abstract Factory для військової стратегії «Пунічні війни». При цьому передбачається, що число і типи бойових одиниць, що створюються в грі, ідентичні для обох армій. Кожна армія має у своєму складі піхотинців (Infantryman), лучників (Archer) та кавалерію (Horseman).

Структура патерну для цього випадку представлена ​​на рис. 36.

ArmyFactory

Infantryman

Roman Infantryman

Carthaginianlnfantryman

InfantrymanO

Archer()

Horseman Про

- Carthaginian ArmyFactory

Roman ArmyFactory

Carthaginianlnfantryman()

CarthaginianArcher()

CarthaginianHorseman()

RomanInfantryman()

- -> RomanArcher

Archer

Carthaginian Archer

CarthaginianHorscnian

Мал. 36. UML-діаграма класів для військової стратегії «Пунічні війни»

// Абстрактні базові класи всіх можливих видів воїнів class Infantryman

virtual void info() = 0; virtual ~Infantryman() ()

virtual void info() = 0; virtual ~Archer() ()

virtual void info() = 0; virtual ~Horseman() ()

// Класи всіх видів воїнів римської армії class Romanlnfantryman: public Infantryman (

public: void info() (

class RomanArcher: public Archer

public: void info() (

class RomanHorseman: public Horseman

public: void info() (

// Класи всіх видів воїнів армії Карфагена class Carthaginianlnfantryman: public Infantryman

public: void info() (

class CarthaginianArcher: public Archer

public: void info() (

class CarthaginianHorseman: public Horseman

public: void info() (

//Абстрактна фабрика для виробництва воїнів class ArmyFactory (

virtual Infantryman* createlnfantryman() = 0; virtual Archer* createArcher() = 0; virtual Horseman* createHorseman() = 0; virtual ~ArmyFactory() ()

// Фабрика для створення воїнів римської армії class RomanArmyFactory: public ArmyFactory (

Infantryman* createlnfantryman() ( return new Romanlnfantryman;

Archer* createArcher() ( return new RomanArcher;

Horseman* createHorseman() ( return new RomanHorseman;

// Фабрика для створення воїнів армії Карфагена class CarthaginianArmyFactory: public ArmyFactory (

Infantryman* createlnfantryman() ( return new Carthaginianlnfantryman;

Archer* createArcherQ ( return new CarthaginianArcher;

Horseman* createHorsemanQ ( return new Carthaginian Horseman;

~Army() ( int i;

void info() ( int i;

for(i=0; i info(); for(i=0; i info(); for(i=0; i info());

vector vi; vector va; vector vh;

// Тут створюється армія тієї чи іншої сторони.

Army* createArmy(ArmyFactory& factory) (Army* p = new Army;

p->vi.push_back(factory.createInfantryman()); p-> va.push_back(factory.createArcher()); p->vh.push_back(factory.createHorseman()); return p;

RomanArmyFactory ra_factory; CarthaginianArmyFactory ca_factory;

Army * га = game.createArmy(ra_factory);

Army * са = game.createArmy(ca_factory); co?t info();

Виведення програми буде наступним:

Roman Infantryman

Carthaginian army:

Carthaginianlnfantryman

CarthaginianArcher

Carthaginian Horseman

Переваги патерну Abstract Factory

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

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

Недоліки патерну Abstract Factory

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

Споріднені патерни

Класи Abstract Factory часто реалізуються фабричними методами (патерн Фабричний метод), але можуть бути реалізовані і за допомогою патерну Прототип. Конкретна фабрика може бути описана патерном Одиночка.

Factory Method дає можливість підкласам створювати деякі класи за допомогою загального інтерфейсу, причому саме спадкоємці визначають, який батьківський об'єкт слід реалізувати. Тобто, потрібен якийсь загальний інтерфейс. Цим інтерфейсом у мові програмування C# то, можливо абстрактний клас чи інтерфейс.

Уявіть собі такий абстрактний клас:

Abstract class Product ( public abstract decimal PurchasePrice (get; set;) public abstract decimal Price (get; set;) public abstract string Description (get; set;) )

Якщо ми успадкуємо цей клас, маємо дотримуватися принципу поліморфізму. Тобто перевизначити всі властивості цього класу, використовуючи слово override. Давайте так і зробимо. Створюємо клас, успадкований від класу Product, який інкапсулюватиме логіку конкретного продукту:

Class Computer: Product ( private decimal _purchase_price; private decimal _price; private string _description; public Computer() : this(null) ( ) public Computer(string _description) : this(_description, 0) ( ) public Computer _purchase_price) : this (_description, _purchase_price, 0) ( ) public Computer(string _description, decimal _purchase_price, decimal _price) ( this._description = _description; this._purchase_price = _; ride string Description ( get ( return _description; ) set ( _description = value; ) ) public override decimal Price ( get ( return _price; ) set ( _price = value; ) ) public override decimal PurchasePrice ( get ( return _purchase_pri ) ) )

Клас Product - призначений визначення інтерфейсу об'єктів, створюваних фабричним методом. Це базова оболонка для продуктів. Продукт має ціну і т.д. Якщо хочете, допишіть у клас Product ще пару властивостей, методів і перевизначте їх у класі, що успадковується. Сподіваюся, що поки що все ясно. Перш ніж я продовжу, скажу пару слів про сам патерн. Зазвичай ми використовуємо конкретний клас і пишемо ось так:

Computer computer = new Computer();

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

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

Ось що мається на увазі:

На діаграмі додалося ще два класи. Наслідуючи шаблон, повинен бути ще один абстрактний клас, в якому і буде фабричний метод. Це хіба що фабрика, що створює продукти конкретного виду. Коли визначено абстрактний клас Creator з фабричним методом, ми можемо створити успадкований клас для конкретного продукту. Щоб легше було зрозуміти, спростимо діаграму:

Ось і всі пироги. Є абстрактний клас продукту та творця, у якому є фабричний метод. Створюємо клас конкретного продукту (успадкований від Product) рас, створюємо конкретний клас творець конкретного продукту (успадкований від Creator) два.

Ось який вигляд має абстрактний клас Creator:

Abstract class Creator ( public abstract Product FactoryMethod(); public abstract Product FactoryMethod(string _description); public abstract Product FactoryMethod(string _description, decimal _purchase_price); У класі, я визначив методи для всіх видів конструкторів класу Computer. А ось творець-клас для класу Computer: class ComputerCreator: Creator ( public override Product FactoryMethod() ( return new Computer(); ) public override Product FactoryMethod(string _description) string _description, decimal _purchase_price) ( return new Computer(_description, _purchase_price); ) Public override Product FactoryMethod(string _description, decimal _purchase_price, decimal _price)

Все, тепер у нас все як на рис. 1. Бачимо, що промислові способи перевантажені, щоб була можливість викликати необхідний конструктор класу Computer .

Створимо ще один клас CDPlayer та клас творець для нього аналогічним чином:

Клас CDPlayer:

Class CDPlayer: Product ( private decimal _purchase_price; // ціна закупівлі private decimal _price; // ціна продажу // масив форматів, які підтримує сд плеєр private string _description; public CDPlayer() : this(null) ( ) public CDPlayer(string _description ) : this(_description, 0) ( ) public CDPlayer(string _description, decimal _purchase_price) : this(_description, _purchase_price, 0) ( ) public CDPlayer(string _description, decimal _purchase_price, decimal ._purchase_price = _purchase_price; this._price = _price;) public override string public override decimal PurchasePrice ( get ( return _purchase_price; ) set ( _purchase_price = value;) ) )

Творець-клас для класу CDPlayer:

Class CDPlayerCreator: Creator ( public override Product FactoryMethod() ( return new CDPlayer(); ) public override Product FactoryMethod (string _description) ( return new CDPlayer(_description); ) public override Product Factory CDPlayer(_description, _purchase_price); ) public override Product FactoryMethod(string _description, decimal _purchase_price, decimal _price)

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

Static void Main(string args) ( List ProductList = новий List ();

Creator creators = новий Creator;

creators = new ComputerCreator(); creators = new CDPlayerCreator(); foreach (Creator cr in creators) ( if (cr is ComputerCreator) productList.Add(cr.FactoryMethod("Ноут бук", 600, 800)); if (cr is CDPlayerCreator) productList.Add(cr.FactoryMethod("au mp3,mp4",250,360)); ) foreach (Product pr in productList) ( Console.WriteLine("Об'єкт класу (0);\n" + "Опис: (1);\n" + "Закупівельна ціна: (2) );\n" + "Ціна продажу: (3);\n", pr.GetType().Name, pr.Description, pr.PurchasePrice, pr.Price); ) Console.ReadLine(); )

Ось результат програми:

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

  • Абстрактний клас для об'єктів -> навпаки клас, який його реалізує для своїх цілей.
  • Абстрактний клас для створення об'єктів -> навпаки клас, який його реалізує для створення своїх об'єктів.
Іншими словами:

Product- Власне продукт. Призначений визначення інтерфейсу об'єктів, створюваних фабричним методом;

ConcreteProduct(Computer, CDPlayer) - конкретні продукти, що беруть участь у схемі, та відповідають за реалізацію абстрактного класу (інтерфейсу) Product.

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

ConcreteCreator- конкретний автор. Тут очевидно: конкретна реалізація творця займається тим, що повертає конкретний продукт. У нашому прикладі дві конкретні реалізації творця - ComputerCreator та CDPlayerCreator.

Автор довіряє своїм підкласам реалізацію відповідного онкретного товару. У цьому полягає суть Factory Method.

Тепер відзначимо плюси та мінуси цього патерну:

Найочевидніший недолік Factory Method – необхідність створювати спадкоємця Creator завжди, коли планується отримати новий тип продукту (тобто новий ConcreteProduct). І цього, на жаль, не уникнути. Але подібна проблема присутня в багатьох шаблонах, що породжують. До переваг варто віднести можливість створювати об'єкти більш універсально, не орієнтуючись на конкретні класи і оперуючи загальним інтерфейсом.

Призначення патерну Factory Method

У системі часто потрібно створювати об'єкти різних типів. Паттерн Factory Method (фабричний метод) може бути корисним у вирішенні наступних завдань:

  • Система повинна залишатися розширюваною шляхом додавання нових типів об'єктів. Безпосереднє використання виразу new є небажаним, оскільки в цьому випадку код створення об'єктів із зазначенням конкретних типів може бути розкиданим по всьому додатку. Тоді такі операції як додавання до системи об'єктів нових типів або заміна об'єктів одного типу на інший будуть скрутними (докладніше в розділі Патерни, що породжують). Паттерн Factory Method дозволяє системі залишатися незалежною як від процесу породження об'єктів, і від їх типів.
  • Наперед відомо, коли потрібно створювати об'єкт, але невідомий його тип.

Опис патерну Factory Method

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

Для забезпечення щодо простого додавання до системи нових типів патерн Factory Method локалізує створення об'єктів конкретних типів у спеціальному класі-фабриці. Методи цього, з яких створюються об'єкти конкретних класів, називаються фабричними. Існують два різновиди патерну Factory Method:

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

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

Реалізація патерну Factory Method

Розглянемо обидва варіанти реалізації патерну Factory Method з прикладу процесу породження військових персонажів нашої стратегічної гри. Її докладний опис можна знайти в розділі Патерни , що породжують . Для спрощення демонстраційного коду створюватимемо військові персонажі для певної абстрактної армії без урахування особливостей воюючих сторін.

Реалізація патерну Factory Method на основі узагальненого конструктора

//#include #include enum Warrior_ID (Infantryman_ID=0, Archer_ID, Horseman_ID); // Ієрархія класів ігрових персонажів class Warrior (public: virtual void info() = 0; virtual ~Warrior() () // Параметризований статичний фабричний метод static Warrior* createWarrior(Warrior_ID id);); class Infantryman: public Warrior ( public: void info() ( cout<< "Infantryman" << endl; } }; class Archer: public Warrior { public: void info() { cout << "Archer" << endl; } }; class Horseman: public Warrior { public: void info() { cout << "Horseman" << endl; } }; // Реализация параметризированного фабричного метода Warrior* Warrior::createWarrior(Warrior_ID id) { Warrior * p; switch (id) { case Infantryman_ID: p = new Infantryman(); break; case Archer_ID: p = new Archer(); break; case Horseman_ID: p = new Horseman(); break; default: assert(false); } return p; }; // Создание объектов при помощи параметризированного фабричного метода int main() { vectorv; v.push_back(Warrior::createWarrior(Infantryman_ID));

Представлений варіант патерну Factory Method користується популярністю завдяки своїй простоті. У ньому статичний фабричний метод createWarrior() визначено у поліморфному базовому класі Warrior . Цей фабричний метод є параметризованим, тобто для створення об'єкта деякого типу createWarrior() передається відповідний ідентифікатор типу.

З точки зору "чистоти" об'єктно-орієнтованого коду цей варіант має такі недоліки:

  • Так як код зі створення об'єктів всіх можливих типів зосереджений у статичному фабричному методі класу Warrior, то базовий клас Warrior має знання про всі похідні від нього класи, що є нетиповим для об'єктно-орієнтованого підходу.
  • Подібне використання оператора switch (як у коді фабричного методу createWarrior()) в об'єктно-орієнтованому програмуванні також не вітається.

Зазначені недоліки відсутні у класичній реалізації патерну Factory Method.

Класична реалізація патерну Factory Method

//#include #include // Ієрархія класів ігрових персонажів class Warrior (public: virtual void info() = 0; virtual ~Warrior() ()); class Infantryman: public Warrior ( public: void info() ( cout<< "Infantryman" << endl; }; }; class Archer: public Warrior { public: void info() { cout << "Archer" << endl; }; }; class Horseman: public Warrior { public: void info() { cout << "Horseman" << endl; }; }; // Фабрики объектов class Factory { public: virtual Warrior* createWarrior() = 0; virtual ~Factory() {} }; class InfantryFactory: public Factory { public: Warrior* createWarrior() { return new Infantryman; } }; class ArchersFactory: public Factory { public: Warrior* createWarrior() { return new Archer; } }; class CavalryFactory: public Factory { public: Warrior* createWarrior() { return new Horseman; } }; // Создание объектов при помощи фабрик объектов int main() { InfantryFactory* infantry_factory = new InfantryFactory; ArchersFactory* archers_factory = new ArchersFactory ; CavalryFactory* cavalry_factory = new CavalryFactory ; vectorv; v.push_back(Warrior::createWarrior(Infantryman_ID));

v.push_back(infantry_factory->createWarrior());

v.push_back(archers_factory->createWarrior());

  • v.push_back(cavalry_factory->createWarrior());
  • for(int i=0; i

Класичний варіант патерну Factory Method використовує ідею поліморфної фабрики. Спеціально виділений створення об'єктів поліморфний базовий клас Factory оголошує інтерфейс фабричного методу createWarrior() , а похідні класи його реалізують.

Представлений варіант патерну Factory Method є найпоширенішим, але не єдиним. Можливі такі варіації:

  • Клас Factory має реалізацію фабричного методу createWarrior() за умовчанням.

Фабричний метод createWarrior() класу Factory параметризований типом створюваного об'єкта (як і представленого раніше, простого варіанта Factory Method) і має реалізацію за умовчанням. У цьому випадку похідні від Factory класи необхідні лише для того, щоб визначити нестандартну поведінку createWarrior() .

  • У разі класичного варіанта патерну навіть для породження єдиного об'єкта необхідно створювати відповідну фабрику

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

Проблема

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

Якоїсь миті ваша програма стає настільки відомою, що морські перевізники вишиковуються в чергу і просять додати підтримку морської логістики в програму.


Додати новий клас не так просто, якщо весь код вже зав'язаний на конкретні класи.

Чудові новини, правда?! Але як щодо коду? Більшість існуючого коду жорстко прив'язана до класів Вантажівок. Щоб додати до програми класи морських суден, знадобиться перелопатити всю програму. Більше того, якщо ви потім вирішите додати в програму ще один вид транспорту, то всю цю роботу доведеться повторити.

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

Рішення

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


Підкласи можуть змінювати клас об'єктів, що створюються.

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

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


Усі об'єкти-продукти повинні мати спільний інтерфейс.

Наприклад, класи Вантажівка та Судно реалізують інтерфейс Транспорт з методом доставити. Кожен із цих класів реалізує метод по-своєму: вантажівки везуть вантажі землею, а судна - морем. Фабричний метод у класі Дорожньої Логістики поверне об'єкт-вантажівка, а клас Морської Логістики – об'єкт-судно.


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

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

Структура



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

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

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

    Найчастіше фабричний метод оголошують абстрактним, щоб змусити всі підкласи реалізувати його по-своєму. Але він може повертати і стандартний продукт.

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

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

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

Псевдокод

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


Приклад крос-платформного діалогу.

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

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

Такий підхід можна застосувати для створення інших елементів інтерфейсу. Хоча кожен новий тип елементів буде наближати вас до абстрактної заводу .

// Паттерн Фабричний метод застосуємо тоді, як у програмі // є ієрархія класів товарів. interface Button is method render() method onClick(f) class WindowsButton implements Button is method render(a, b) is // Відобразити кнопку в стилі Windows.

method onClick(f) is // Навісити на кнопку обробник подій Windows. class HTMLButton implements Button is method render(a, b) is // Повернути HTML-код кнопки.

method onClick(f) is // Навісити на кнопку обробник події браузера. // Базовий клас заводу. Зауважте, що "фабрика" - це всього лише додаткова роль для класу. Швидше за все, він вже має якусь бізнес-логіку, в якій потрібно створення різноманітних продуктів. class Dialog is method render() is // Щоб використовувати фабричний метод, ви повинні // переконатися, що ця бізнес-логіка залежить від // конкретних класів продуктів. Button - це загальний інтерфейс кнопок, тому все добре.

Button okButton = createButton() okButton.onClick(closeDialog) okButton.render() // Ми виносимо весь код створення продуктів у особливий метод, // який називають "фабричним".

abstract method createButton() // Конкретні фабрики перевизначають фабричний метод і // повертають із нього власні продукти. class WindowsDialog extends Dialog is method createButton() is return new WindowsButton() class WebDialog extends Dialog is method createButton() is return new HTMLButton() class Application is field dialog: Dialog // Додаток створює певну фабрику в залежності оточення.

Коли ви хочете надати можливість користувачам розширювати частини вашого фреймворку чи бібліотеки.

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

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

Наприклад, ви використовуєте готовий UI-фреймворк для своєї програми. Але біда - потрібно мати круглі кнопки, замість стандартних прямокутних. Ви створюєте клас RoundButton. Але як сказати головному класу фреймворку UIFramework, щоб він тепер створював круглі кнопки замість стандартних?

Для цього ви створюєте підклас UIWithRoundButtons з базового класу фреймворку, перевизначаєте у ньому метод створення кнопки (а-ля createButton) і вписуєте туди створення свого класу кнопок. Потім використовуєте UIWithRoundButtons замість стандартного UIFramework.

Коли ви хочете економити системні ресурси, повторно використовуючи вже створені об'єкти замість створення нових.

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

Уявіть, скільки дій потрібно зробити, щоб повторно використовувати існуючі об'єкти:

  1. Спочатку вам слід створити спільне сховище, щоб зберігати в ньому всі об'єкти, що створюються.
  2. При запиті нового об'єкта потрібно буде заглянути в сховище і перевірити, чи є об'єкт, що не використовується.
  3. А потім повернути його клієнтському коду.
  4. Але якщо вільних об'єктів немає – створити новий, не забувши додати його до сховища.

Весь цей код потрібно кудись помістити, щоб не засмічувати клієнтський код.

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

Отже, потрібен інший метод, який віддавав як існуючі, і нові об'єкти. Ним і стане фабричний метод.

Кроки реалізації

    Приведіть усі продукти до спільного інтерфейсу.

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

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

    У фабричний метод, можливо, доведеться додати кілька параметрів, які контролюють, який із продуктів потрібно створити.

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

    Для кожного типу продуктів заведіть підклас та перевизначте у ньому фабричний метод. Перемістіть код створення відповідного продукту з суперкласу.

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

    Наприклад, у вас є клас Пошта з підкласами АвіаПошта та Наземна Пошта, а також класи продуктів Літак, Вантажівка та Поїзд. Авіа відповідає Літакам, але для Наземної Пошти є відразу два продукти. Ви можете створити новий підклас пошти для поїздів, але проблему можна вирішити і по-іншому. Клієнтський код може передавати у фабричний метод Наземної Пошти аргумент, який контролює тип створюваного продукту.

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

    Фабричний метод, навпаки, побудований на спадкування, але не потребує складної ініціалізації.

    Фабричний метод можна як окремий випадок Шаблонного методу . Крім того, Фабричний методнерідко буває частиною великого класу з Шаблонними методами.


Top