Перейти до вмісту

Фабричний метод (шаблон проєктування)

Матеріал з Вікіпедії — вільної енциклопедії.
(Перенаправлено з Фабричний метод)

Фабричний метод (англ. Factory Method) — шаблон проєктування, належить до класу твірних шаблонів.

Призначення

[ред. | ред. код]

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

Застосування

[ред. | ред. код]

Слід використовувати шаблон Фабричний метод коли:

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

Структура

[ред. | ред. код]
UML діаграма, що описує структуру шаблону проєктування Фабричний метод
  • Product — продукт:
    • визначає інтерфейс об'єктів, що створюються фабричним методом;
  • ConcreteProduct — конкретний продукт:
    • реалізує інтерфейс Product;
  • Creator — творець:
    • оголошує фабричний метод, що повертає об'єкт класу Product. Creator може також визначати реалізацію за умовчанням фабричного методу, що повертає об'єкт ConcreteProduct;
    • може викликати фабричний метод для створення об'єкта Product;
  • ConcreteCreator — конкретний творець:
    • заміщує фабричний метод, що повертає об'єкт ConcreteProduct.

Переваги

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

Недоліки

[ред. | ред. код]
  • необхідність створювати спадкоємця Creator для кожного нового типу продукту (ConcreteProduct).

Стосунки

[ред. | ред. код]

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

Реалізація

[ред. | ред. код]

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

Реалізація мовою Python
#coding: utf-8

class Culture:
    def __repr__(self):
        return self.__str__()

class Democracy(Culture):
    def __str__(self):
        return 'Democracy'

class Dictatorship(Culture):
    def __str__(self):
        return 'Dictatorship'

class Government:
    culture = ''
    def __str__(self):
        return self.culture.__str__()

    def __repr__(self):
        return self.culture.__repr__()

    def set_culture(self):
        raise AttributeError('Not Implemented Culture')

class GovernmentA(Government):
    def set_culture(self):
        self.culture = Democracy()

class GovernmentB(Government):
    def set_culture(self):
        self.culture = Dictatorship()

g1 = GovernmentA()
g1.set_culture()
print (str(g1))

g2 = GovernmentB()
g2.set_culture()
print (str(g2))
Реалізація мовою Java
abstract class Product { }

class ConcreteProductA extends Product { }

class ConcreteProductB extends Product { }

abstract class Creator {
    public abstract Product factoryMethod();
}

class ConcreteCreatorA extends Creator {
    @Override
    public Product factoryMethod() { return new ConcreteProductA(); }
}

class ConcreteCreatorB extends Creator {
    @Override
    public Product factoryMethod() { return new ConcreteProductB(); }
}

public class FactoryMethodExample {
    public static void main(String[] args) {
        // an array of creators
        Creator[] creators = {new ConcreteCreatorA(), new ConcreteCreatorB()};
        // iterate over creators and create products
        for (Creator creator: creators) {
            Product product = creator.factoryMethod();
            System.out.printf("Created {%s}\n", product.getClass());
        }
    }
}

Результат роботи:

Created {class ConcreteProductA}
Created {class ConcreteProductB}
Реалізація мовою C++
#include <iostream>
#include <string>
using namespace std;

class Product
{
public:
    virtual string getName() = 0;
    virtual ~Product(){}
};

class ConcreteProductA: public Product
{
public:
    string getName() { return "ConcreteProductA"; }
};

class ConcreteProductB : public Product
{
public:
    string getName() { return "ConcreteProductB"; }
};

class Creator
{
public: 
    virtual Product* factoryMethod() = 0;
};

class ConcreteCreatorA: public Creator
{
public:
    Product* factoryMethod()
    {
        return new ConcreteProductA();
    }
};

class ConcreteCreatorB : public Creator
{
public: 
    Product* factoryMethod()
    {
        return new ConcreteProductB();
    }
};

int main()
{
    static const size_t count = 2;
    ConcreteCreatorA CreatorA;
    ConcreteCreatorB CreatorB;
    // An array of creators
    Creator* creators[count] = {&CreatorA,&CreatorB};
    // Iterate over creators and create products
    for (size_t i = 0; i < count; i++) {
        Product* product = creators[i]->factoryMethod();
        cout << product->getName() << endl;
        delete product;
    }
    return 0;
}
Реалізація мовою C#
using System;
using System.Collections.Generic;
 
namespace Factory
{
    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(); }
    }

    class ConcreteCreatorB : Creator{
        public override Product FactoryMethod() { return new ConcreteProductB(); }
    }

    public class MainApp
    {
        public static void Main()
        {
          // an array of creators
          Creator[] creators = {new ConcreteCreatorA(), new ConcreteCreatorB()};
          // iterate over creators and create products
          foreach (Creator creator in creators){
              Product product = creator.FactoryMethod();
              Console.WriteLine("Created {0}", product.GetType());
          }
          // Wait for user
          Console.Read();
        }
    }
}
Реалізація мовою C#
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;

namespace FactoryMethod
{
    // ускладнимо архітектуру
    // але забезпечимо повну незалежність класів у ієрархії

    // створює продукт
    public interface ICreator<out TReturnValue>
    {
        TReturnValue Create();
    }
    // фабрика
    // реєструє, створює продукт
    public interface IFactory<in TKey, in TRegValue, out TReturnValue> 
        where TRegValue : ICreator<TReturnValue>
    {
        void Registrate(TKey key, TRegValue value);
        void UnRegistrate(TKey key);
        TReturnValue MakeInstance(TKey key);
    }
    // ініціалізатор фабрики
    // наповнює фабрику початковими значеннями
    public interface IFactoryInitializer<in TFactory>
    {
        void Initialize(TFactory factory);
    }
    // узагальнений клас фабрики, 
    public class FactoryBase<TKey, TReturnValue> : IFactory<TKey, ICreator<TReturnValue>, TReturnValue>
    {
        // FIELDS
        IDictionary<TKey, ICreator<TReturnValue>> factory;

        // CONSTRUCTORS
        public FactoryBase()
        {
            factory = new ConcurrentDictionary<TKey, ICreator<TReturnValue>>();
        }

        // METHODS
        public TReturnValue MakeInstance(TKey key)
        {
            // checks
            if (key == null) throw new ArgumentNullException(nameof(key));
            if (!factory.ContainsKey(key)) throw new InvalidOperationException($"No such key '{key}'");

            // make instance
            return factory[key].Create();
        }

        public void Registrate(TKey key, ICreator<TReturnValue> creator)
        {
            // checking argument
            // key
            if (key == null) throw new ArgumentNullException(nameof(key));
            if (factory.ContainsKey(key)) throw new InvalidOperationException($"Type by key '{key}' has been already registered");
            // value
            if (creator == null) throw new ArgumentNullException(nameof(creator));
            Type creatorType = creator.GetType();
            if (creatorType.IsInterface || creatorType.IsAbstract) throw new ArgumentException(nameof(creator));

            // adding
            factory.Add(key, creator);
        }

        public void UnRegistrate(TKey key)
        {
            // checking argument
            // key
            if (key == null) throw new ArgumentNullException(nameof(key));
            if (!factory.ContainsKey(key)) throw new InvalidOperationException($"No such key '{key}'");

            // removing 
            factory.Remove(key);
        }
    }

    // SHAPES
    // ієрархія класів
    abstract class ShapeBase { }
    class Square : ShapeBase { }
    class Circle : ShapeBase { }
    class Rectangle : ShapeBase { }

    // SHAPE CREATORS
    // для кожного класу в ієрархії варто написати 
    // клас відповідальний за створення
    // (в C# не обов'язково, можна використати Activator)
    class SquareCreator : ICreator<Square>
    {
        public Square Create()
        {
            // можливо створення за допомогою 
            // будь-якого, можливо твірного, шаблону проєктування
            return new Square();
        }
    }
    class DefaultCreator<T> : ICreator<T> where T: new()
    {
        public T Create()
        {
            return new T();
        }
    }
    class CircleCreator : DefaultCreator<Circle> { }
    // використовується при реєстрації
    // DefaultCreator<Rectangle> 
    // зменшує кількість класів Creator'ів
    // та деколи краще мати окремі класи Creator'ів для кожного об'єкта
    // так при потребі змінити спосіб створення об'єкта, доведеться змінити лише відповідний метод Create
    // а не створювати новий клас, та шукати усі місця в яких він реєструється

    // FACTORY
    // конкретна фабрика
    class ShapeFactory : FactoryBase<string, ShapeBase>
    {
        public ShapeFactory(IFactoryInitializer<ShapeFactory> factoryInitializer)
        {
            factoryInitializer.Initialize(this);
        }
    }
    // конкретний ініціалізатор
    class DefaultShapeFactoryInitializer : IFactoryInitializer<ShapeFactory>
    {
        public void Initialize(ShapeFactory factory)
        {
            factory.Registrate(nameof(Square),    new SquareCreator());
            factory.Registrate(nameof(Circle),    new CircleCreator());
            factory.Registrate(nameof(Rectangle), new DefaultCreator<Rectangle>());
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ShapeFactory factory = new ShapeFactory(new DefaultShapeFactoryInitializer());
            string[] shapeToCreate = { nameof(Square), nameof(Circle) };

            foreach (string item in shapeToCreate)
            {
                ShapeBase shape = factory.MakeInstance(item);
                Console.WriteLine(shape.ToString());
            }

            Console.ReadLine();
        }
    }
}

JavaScript

[ред. | ред. код]
Реалізація мовою JavaScript
function Product() {this.getName=null;}
 
'use strict';

// Інстанціювання функції
function ConcreteProductA() {
 this.getName = function() {
  // Повертання строки з вказаним змістом 
  return 'ConcreteProductA';
 };
}
 
// Інстанціювання функції
function ConcreteProductB() {
 this.getName = function() {
  // Повертання строки з вказаним змістом 
  return 'ConcreteProductB';
 };
}
 
// Інстанціювання функції
function ConcreteCreatorA() {	
 this.factoryMethod = function() {
  // Повертання нової функції з рядка 7
  return new ConcreteProductA(); 
 };
}
 
// Інстанціювання функції
function ConcreteCreatorB() {
 this.factoryMethod = function() {
  // Повертання нової функції з рядка 
  return new ConcreteProductB();
 };
}

// Створюємо масив функцій
const creators = [new ConcreteCreatorA(), new ConcreteCreatorB()]; 
creators.forEach((el) => {
 console.log(el.factoryMethod().getName());
});
Код мовою PHP
<?php
interface Product{
    public function GetName();
}
 
class ConcreteProductA implements Product{
    public function GetName() { return "ProductA"; }
} 
 
class ConcreteProductB implements Product{
    public function GetName() { return "ProductB"; }
}
 
interface Creator{
    public function FactoryMethod();
} 
 
class ConcreteCreatorA implements Creator{
    public function FactoryMethod() { return new ConcreteProductA(); }
}

class ConcreteCreatorB implements Creator{
    public function FactoryMethod() { return new ConcreteProductB(); }
}

// An array of creators
$creators = array( new ConcreteCreatorA(), new ConcreteCreatorB() );
// Iterate over creators and create products
for($i = 0; $i < count($creators); $i++){
    $products[] = $creators[$i]->FactoryMethod();
}
 
header("content-type:text/plain");
echo var_export($products);
 
?>
Код мовою PHP
<?php
abstract class Animal
{
    // фабричний метод, на основі типу повертаємо об'єкт
    public static function initial($animal)
    {
        return new $animal();
    }
    abstract public function voice();
}

class Lion extends Animal
{
    public function voice()
    {
        echo 'Rrrrrrrr i\'m the lion <br />' . PHP_EOL;
    }
}

class Cat extends Animal
{
    public function voice()
    {
        echo 'Meow, meow i\'m the kitty <br />' . PHP_EOL;
    }
}

$animal1 = Animal::initial('Lion');
$animal2 = Animal::initial('Cat');

$animal1->voice();
$animal2->voice();
Код мовою PHP
<?php

// Загальний Інтерфейс реалізації
interface GuiFactoryInterface
{
	public function buildButton(): ButtonInterface;
	public function buildCheckBox(): CheckBoxInterface;
}

interface ButtonInterface
{
	public function draw();
}

interface CheckBoxInterface
{
	public function draw();
}

// Кінцева реалізація
class ButtonBootstrap implements ButtonInterface
{
	public function draw() {return __CLASS__;}
}

class CheckBoxBootstrap implements CheckBoxInterface
{
	public function draw() {return __CLASS__;}
}

class ButtonSemanticUI implements ButtonInterface
{
	public function draw() {return __CLASS__;}
}

class CheckBoxSemanticUI implements CheckBoxInterface
{
	public function draw() {return __CLASS__;}
}

// Інтерфейси для зв'язку однотипності
// Групи взаємопов'язаних сімейств
class BootstrapFactory implements GuiFactoryInterface
{
	public function buildButton(): ButtonInterface
	{
		return new ButtonBootstrap();
	}

	public function buildCheckBox(): CheckBoxInterface
	{
		return new CheckBoxBootstrap();
	}
}

class SemanticUIFactory implements GuiFactoryInterface
{
	public function buildButton(): ButtonInterface
	{
		return new ButtonSemanticUI();
	}

	public function buildCheckBox(): CheckBoxInterface
	{
		return new CheckBoxSemanticUI();
	}
}

// Опис роботи з об'єктом
abstract class AbstractForm
{
	public function render()
	{
		$guiKit = $this->createGuiKit();
		$result[] = $guiKit->buildCheckBox()->draw();
		$result[] = $guiKit->buildButton()->draw();

		return $result;
	}

	abstract function createGuiKit(): GuiFactoryInterface;
}

class BootstrapDialogForm extends AbstractForm
{
	public function createGuiKit(): GuiFactoryInterface
	{
		return new BootstrapFactory();
	}
}

class SemanticUIDialogForm extends AbstractForm
{
	public function createGuiKit(): GuiFactoryInterface
	{
		return new SemanticUIFactory();
	}
}

function factoryMethod()
{
	$dialogForm = new BootstrapDialogForm();
	// OR
	//$dialogForm = new SemanticUIDialogForm();

	return $dialogForm->render();
}

$renderedDialogForm = factoryMethod();
Реалізація мовою Delphi
program FactoryMethod;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type

// Product
TProduct = class(TObject)
public
  function GetName: string; virtual; abstract;
end;

// ConcreteProductA
TConcreteProductA = class(TProduct)
public
  function GetName: string; override;
end;

// ConcreteProductB
TConcreteProductB = class(TProduct)
public
  function GetName: string; override;
end;

// Creator
TCreator = class(TObject)
public
  function FactoryMethod: TProduct; virtual; abstract;
end;

// ConcreteCreatorA
TConcreteCreatorA = class(TCreator)
public
  function FactoryMethod: TProduct; override;
end;

// ConcreteCreatorB
TConcreteCreatorB = class(TCreator)
public
  function FactoryMethod: TProduct; override;
end;

{ ConcreteProductA }
function TConcreteProductA.GetName: string;
begin
  Result := 'ConcreteProductA';
end;

{ ConcreteProductB }
function TConcreteProductB.GetName: string;
begin
  Result := 'ConcreteProductB';
end;

{ ConcreteCreatorA }
function TConcreteCreatorA.FactoryMethod: TProduct;
begin
  Result := TConcreteProductA.Create;
end;

{ ConcreteCreatorB }
function TConcreteCreatorB.FactoryMethod: TProduct;
begin
  Result := TConcreteProductB.Create;
end;

const
  Count = 2;

var
  Creators: array[1..Count] of TCreator;
  Product: TProduct;
  I: Integer;

begin
  // An array of creators
  Creators[1] := TConcreteCreatorA.Create;
  Creators[2] := TConcreteCreatorB.Create;

  // Iterate over creators and create products
  for I := 1 to Count do
  begin
    Product := Creators[I].FactoryMethod;
    WriteLn(Product.GetName);
    Product.Free;
  end;

  for I := 1 to Count do
    Creators[I].Free;

  ReadLn;
end.
Реалізація мовою Delphi (віртуальні конструктори)
program FactoryMethod;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type

// Product
TProduct = class(TObject)
private
  SubName : string;
public
  function GetName: string; virtual; abstract;
  function GetFullName: string;
  constructor Create; virtual; abstract;
end;

TProductClass = class of TProduct;

// ConcreteProductA
TConcreteProductA = class(TProduct)
public
  function GetName: string; override;
  constructor Create; override;
end;

// ConcreteProductB
TConcreteProductB = class(TProduct)
public
  function GetName: string; override;
  constructor Create; override;
end;

{ TProduct}
function TProduct.GetFullName: string;
begin
  Result := GetName + ' : ' + SubName;
end;

{ ConcreteProductA }
constructor TConcreteProductA.Create;
begin
  inherited;
  SubName := 'Product A subname';
end;

function TConcreteProductA.GetName: string;
begin
  Result := 'ConcreteProductA';
end;

{ ConcreteProductB }
constructor TConcreteProductB.Create;
begin
  inherited;
  SubName := 'Product B subname';
end;

function TConcreteProductB.GetName: string;
begin
  Result := 'ConcreteProductB';
end;

const
  Count = 2;

var
  Creators: array[1..Count] of TProductClass;
  Product: TProduct;
  I: Integer;

begin
  // An array of creators
  Creators[1] := TConcreteProductA;
  Creators[2] := TConcreteProductB;

  // Iterate over creators and create products
  for I := 1 to Count do
  begin
    Product := Creators[I].Create;
    WriteLn(Product.GetFullName);
    Product.Free;
  end;

  ReadLn;
end.

Див. також

[ред. | ред. код]

Джерела

[ред. | ред. код]
  • Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John (1994). Design Patterns: Elements of Reusable Object-Oriented Software (англ.). Addison–Wesley. с. 395. Посилання на книгу.
  • Alan Shallowey, James R. Trott (2004). Design Patterns Explained: A New Perspective on Object-Oriented Design (PDF) (англ.).