Шаблон фабричного метода
В объектно-ориентированном программировании шаблон фабричного метода — это шаблон проектирования , который использует фабричные методы для решения проблемы создания объектов без указания их точного класса . Вместо вызова конструктора это делается путем вызова фабричного метода для создания объекта. Фабричные методы могут быть либо указаны в интерфейсе и реализованы дочерними классами, либо реализованы в базовом классе и при необходимости переопределены классами производными . Это один из 23 классических шаблонов проектирования, описанных в книге « Шаблоны проектирования» (часто называемых «Бандой четырех» или просто «GoF»), который относится к подкатегории « творческий шаблон» . [1]
Обзор
[ редактировать ]Шаблон проектирования «Фабричный метод» решает такие проблемы, как:
- Как можно создать объект, чтобы подклассы могли переопределить его последующую и отдельную реализацию?
- Как можно отложить создание экземпляра объекта до подкласса?
Шаблон проектирования «Фабричный метод» описывает, как решать такие проблемы:
- Определите фабричный метод внутри суперкласса , который передает создание объекта фабричному методу подкласса .
- Создайте объект, вызвав фабричный метод вместо прямого вызова конструктора.
Это позволяет писать подклассы, которые могут изменить способ создания объекта (например, путем переопределения класса для создания экземпляра).
См. также диаграмму классов UML ниже.
Определение
[ редактировать ]«Определите интерфейс для создания объекта, но позвольте подклассам решать, какой класс создавать. Метод Factory позволяет классу отложить создание экземпляра, который он использует, до подклассов». ( Банда четырех )
Создание объекта часто требует сложных процессов, которые нецелесообразно включать в составной объект. Создание объекта может привести к значительному дублированию кода, может потребовать информацию, недоступную для составного объекта, может не обеспечить достаточный уровень абстракции или иным образом может не входить в состав составного объекта . Шаблон проектирования фабричного метода решает эти проблемы, определяя отдельный метод для создания объектов, подклассы которого затем могут переопределить, чтобы указать производный тип продукта, который будет создан.
Шаблон фабричного метода опирается на наследование, поскольку создание объектов делегируется подклассам, которые реализуют фабричный метод для создания объектов. [2] Как показано в примере C# ниже, шаблон фабричного метода также может зависеть от реализации интерфейса — в данном случае IPerson.
Структура
[ редактировать ]Диаграмма классов UML
[ редактировать ]
На приведенной выше UML классов диаграмме
тот Creator
класс, который требует Product
объект не создает экземпляр Product1
класс напрямую.
Вместо этого Creator
относится к отдельному factoryMethod()
для создания объекта продукта,
что делает Creator
независимо от того, какой конкретный класс создается.
Подклассы Creator
может переопределить, какой класс создавать. В этом примере Creator1
подкласс реализует абстрактное factoryMethod()
путем создания экземпляра Product1
сорт.
Примеры
[ редактировать ]Эта реализация C++14 основана на реализации до C++98, описанной в книге. [4]
#include <iostream>
#include <memory>
enum ProductId {MINE, YOURS};
// defines the interface of objects the factory method creates.
class Product {
public:
virtual void print() = 0;
virtual ~Product() = default;
};
// implements the Product interface.
class ConcreteProductMINE: public Product {
public:
void print() {
std::cout << "this=" << this << " print MINE\n";
}
};
// implements the Product interface.
class ConcreteProductYOURS: public Product {
public:
void print() {
std::cout << "this=" << this << " print YOURS\n";
}
};
// declares the factory method, which returns an object of type Product.
class Creator {
public:
virtual std::unique_ptr<Product> create(ProductId id) {
if (ProductId::MINE == id) return std::make_unique<ConcreteProductMINE>();
if (ProductId::YOURS == id) return std::make_unique<ConcreteProductYOURS>();
// repeat for remaining products...
return nullptr;
}
virtual ~Creator() = default;
};
int main() {
// The unique_ptr prevent memory leaks.
std::unique_ptr<Creator> creator = std::make_unique<Creator>();
std::unique_ptr<Product> product = creator->create(ProductId::MINE);
product->print();
product = creator->create(ProductId::YOURS);
product->print();
}
Вывод программы такой
this=0x6e5e90 print MINE
this=0x6e62c0 print YOURS
В игру-лабиринт можно играть в двух режимах: один с обычными комнатами, которые соединены только с соседними комнатами, и другой с волшебными комнатами, которые позволяют игрокам перемещаться в случайном порядке.
Структура
[ редактировать ]
Room
является базовым классом для конечного продукта ( MagicRoom
или OrdinaryRoom
). MazeGame
объявляет абстрактный фабричный метод для производства такого базового продукта. MagicRoom
и OrdinaryRoom
являются подклассами базового продукта, реализующими конечный продукт. MagicMazeGame
и OrdinaryMazeGame
являются подклассами MazeGame
внедрение фабричного метода производства конечной продукции. Таким образом, фабричные методы отделяют вызывающих абонентов ( MazeGame
) от реализации конкретных классов. Это делает «нового» Оператора ненужным, позволяет придерживаться принципа открытости/закрытости и делает конечный продукт более гибким в случае изменений.
Пример реализации
[ редактировать ]С#
[ редактировать ]// Empty vocabulary of actual object
public interface IPerson
{
string GetName();
}
public class Villager : IPerson
{
public string GetName()
{
return "Village Person";
}
}
public class CityPerson : IPerson
{
public string GetName()
{
return "City Person";
}
}
public enum PersonType
{
Rural,
Urban
}
/// <summary>
/// Implementation of Factory - Used to create objects.
/// </summary>
public class PersonFactory
{
public IPerson GetPerson(PersonType type)
{
switch (type)
{
case PersonType.Rural:
return new Villager();
case PersonType.Urban:
return new CityPerson();
default:
throw new NotSupportedException();
}
}
}
В приведенном выше коде вы можете увидеть создание одного интерфейса под названием IPerson
и две реализации, называемые Villager
и CityPerson
. В зависимости от типа, переданного в PersonFactory
объект, мы возвращаем исходный конкретный объект в качестве интерфейса IPerson
.
Фабричный метод — это всего лишь дополнение к PersonFactory
сорт. Он создает объект класса через интерфейсы, но, с другой стороны, он также позволяет подклассу решать, экземпляр какого класса создается.
public interface IProduct
{
string GetName();
bool SetPrice(double price);
}
public class Phone : IProduct
{
private double _price;
public string GetName()
{
return "Apple TouchPad";
}
public bool SetPrice(double price)
{
_price = price;
return true;
}
}
/* Almost same as Factory, just an additional exposure to do something with the created method */
public abstract class ProductAbstractFactory
{
protected abstract IProduct MakeProduct();
public IProduct GetObject() // Implementation of Factory Method.
{
return this.MakeProduct();
}
}
public class PhoneConcreteFactory : ProductAbstractFactory
{
protected override IProduct MakeProduct()
{
IProduct product = new Phone();
// Do something with the object after you get the object.
product.SetPrice(20.30);
return product;
}
}
Вы можете видеть, что мы использовали MakeProduct
на бетонном заводе. В результате вы можете легко позвонить MakeProduct()
из него, чтобы получить IProduct
. Вы также можете написать свою собственную логику после получения объекта в конкретном фабричном методе. GetObject сделан абстрактным в интерфейсе Factory.
Ява
[ редактировать ]Этот пример Java похож на пример из книги « Шаблоны проектирования» .
MazeGame использует комнаты, но возлагает ответственность за создание комнат на свои подклассы, которые создают конкретные классы. В обычном игровом режиме можно использовать этот шаблонный метод:
public abstract class Room {
abstract void connect(Room room);
}
public class MagicRoom extends Room {
public void connect(Room room) {}
}
public class OrdinaryRoom extends Room {
public void connect(Room room) {}
}
public abstract class MazeGame {
private final List<Room> rooms = new ArrayList<>();
public MazeGame() {
Room room1 = makeRoom();
Room room2 = makeRoom();
room1.connect(room2);
rooms.add(room1);
rooms.add(room2);
}
abstract protected Room makeRoom();
}
В приведенном выше фрагменте MazeGame
Конструктор — это шаблонный метод , который реализует некоторую общую логику. Это относится к makeRoom
фабричный метод, который инкапсулирует создание комнат, позволяющих использовать другие комнаты в подклассе. Чтобы реализовать другой игровой режим с волшебными комнатами, достаточно переопределить makeRoom
метод:
public class MagicMazeGame extends MazeGame {
@Override
protected MagicRoom makeRoom() {
return new MagicRoom();
}
}
public class OrdinaryMazeGame extends MazeGame {
@Override
protected OrdinaryRoom makeRoom() {
return new OrdinaryRoom();
}
}
MazeGame ordinaryGame = new OrdinaryMazeGame();
MazeGame magicGame = new MagicMazeGame();
PHP
[ редактировать ]Далее следует еще один пример на PHP , на этот раз с использованием реализации интерфейса вместо создания подклассов (однако того же можно добиться с помощью создания подклассов). Важно отметить, что фабричный метод также может быть определен как общедоступный и вызываться непосредственно клиентским кодом (в отличие от приведенного выше примера Java).
/* Factory and car interfaces */
interface CarFactory
{
public function makeCar(): Car;
}
interface Car
{
public function getType(): string;
}
/* Concrete implementations of the factory and car */
class SedanFactory implements CarFactory
{
public function makeCar(): Car
{
return new Sedan();
}
}
class Sedan implements Car
{
public function getType(): string
{
return 'Sedan';
}
}
/* Client */
$factory = new SedanFactory();
$car = $factory->makeCar();
print $car->getType();
Питон
[ редактировать ]То же, что и пример Java.
from abc import ABC, abstractmethod
class MazeGame(ABC):
def __init__(self) -> None:
self.rooms = []
self._prepare_rooms()
def _prepare_rooms(self) -> None:
room1 = self.make_room()
room2 = self.make_room()
room1.connect(room2)
self.rooms.append(room1)
self.rooms.append(room2)
def play(self) -> None:
print(f"Playing using {self.rooms[0]}")
@abstractmethod
def make_room(self):
raise NotImplementedError("You should implement this!")
class MagicMazeGame(MazeGame):
def make_room(self) -> "MagicRoom":
return MagicRoom()
class OrdinaryMazeGame(MazeGame):
def make_room(self) -> "OrdinaryRoom":
return OrdinaryRoom()
class Room(ABC):
def __init__(self) -> None:
self.connected_rooms = []
def connect(self, room: "Room") -> None:
self.connected_rooms.append(room)
class MagicRoom(Room):
def __str__(self) -> str:
return "Magic room"
class OrdinaryRoom(Room):
def __str__(self) -> str:
return "Ordinary room"
ordinaryGame = OrdinaryMazeGame()
ordinaryGame.play()
magicGame = MagicMazeGame()
magicGame.play()
Использование
[ редактировать ]- В ADO.NET IDbCommand.CreateParameter . является примером использования фабричного метода для соединения параллельных иерархий классов
- В Qt заархивированный QMainWindow::createPopupMenu, 19 июля 2015 г. на Wayback Machine, является фабричным методом, объявленным в платформе, который можно переопределить в коде приложения .
- В Java используется несколько фабрик в пакете javax.xml.parsers . например javax.xml.parsers.DocumentBuilderFactory или javax.xml.parsers.SAXParserFactory.
- В HTML5 DOM API интерфейс Document содержит фабричный метод createElement для создания определенных элементов интерфейса HTMLElement.
См. также
[ редактировать ]- «Шаблоны проектирования» , очень влиятельная книга
- Шаблон проектирования , обзор шаблонов проектирования в целом
- Абстрактный фабричный шаблон , шаблон, часто реализуемый с использованием фабричных методов.
- Шаблон «Строитель» , еще один шаблон создания.
- Шаблон метода шаблона , который может вызывать фабричные методы.
- Джошуа Блоха Идея о статическом фабричном методе , которая, по его словам, не имеет прямого эквивалента в шаблонах проектирования .
Примечания
[ редактировать ]- ^ Гамма и др. 1994 , с. 107.
- ^ Фриман, Эрик; Фриман, Элизабет; Кэти, Сьерра; Берт, Бейтс (2004). Хендриксон, Майк; Лукидес, Майк (ред.). Шаблоны проектирования Head First (мягкая обложка) . Том. 1. О'РЕЙЛИ. п. 162. ИСБН 978-0-596-00712-6 . Проверено 12 сентября 2012 г.
- ^ «Шаблон проектирования фабричного метода — структура и сотрудничество» . w3sDesign.com . Проверено 12 августа 2017 г.
- ^ Гамма и др. 1994 , с. 122.
Ссылки
[ редактировать ]- Мартин Фаулер ; Кент Бек ; Джон Брант ; Уильям Опдайк ; Дон Робертс (июнь 1999 г.). Рефакторинг: улучшение дизайна существующего кода . Аддисон-Уэсли. ISBN 0-201-48567-2 .
- Гамма, Эрих ; Хельм, Ричард ; Джонсон, Ральф ; Влиссидес, Джон (1994). Шаблоны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования . Аддисон-Уэсли. ISBN 0-201-63361-2 .
- Кокс, Брэд Дж. (1986). Объектно-ориентированное программирование: эволюционный подход . Аддисон-Уэсли. ISBN 978-0-201-10393-9 .
- Коэн, Таль; Гил, Джозеф (2007). «Лучшее строительство с помощью заводов» (PDF) . Журнал объектных технологий . 6 (6). Бертран Мейер : 103. doi : 10.5381/jot.2007.6.6.a3 . Проверено 12 марта 2007 г.
Внешние ссылки
[ редактировать ]
- Шаблон проектирования фабрики. Архивировано 10 января 2018 г. на сайте Wayback Machine Implementation на Java.
- Фабричный метод в UML и LePUS3 (язык описания проекта)
- Рассмотрим статические фабричные методы Джошуа Блоха.