Шаблон заводского метода - Factory method pattern

В программировании на основе классов , то модель фабричного метода является Творением модели , которая использует фабричные методы для решения проблемы создания объектов без указания точного класса объекта , который будет создан. Это делается путем создания объектов путем вызова фабричного метода - либо указанного в интерфейсе и реализуемого дочерними классами, либо реализованного в базовом классе и необязательно переопределенного производными классами, а не путем вызова конструктора .

Обзор

Шаблон проектирования Factory Method - один из двадцати трех хорошо известных шаблонов проектирования «Банда четырех», которые описывают, как решать повторяющиеся проблемы проектирования для разработки гибкого и многоразового объектно-ориентированного программного обеспечения, то есть объектов, которые легче реализовать, изменение, тестирование и повторное использование.

Шаблон проектирования Factory Method решает такие проблемы, как:

  • Как можно создать объект, чтобы подклассы могли переопределить, какой класс создавать?
  • Как класс может отложить создание экземпляров до подклассов?

Шаблон проектирования Factory Method описывает, как решать такие проблемы:

  • Определите отдельную операцию ( фабричный метод ) для создания объекта.
  • Создайте объект, вызвав фабричный метод .

Это позволяет писать подклассы, чтобы изменить способ создания объекта (чтобы переопределить класс для создания экземпляра).
См. Также диаграмму классов UML ниже.

Определение

«Определите интерфейс для создания объекта, но позвольте подклассам решать, какой класс создать. Метод Factory позволяет классу отложить создание экземпляра, которое он использует, до подклассов». ( Банда четырех )

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

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

Структура

Диаграмма классов UML

Пример диаграммы классов UML для шаблона проектирования Factory Method.

На приведенной выше диаграмме классов UML класс , Creatorкоторому требуется Productобъект, не создает экземпляр Product1класса напрямую. Вместо этого Creatorссылка на отдельный factoryMethod()для создания объекта продукта делает Creatorнезависимым от того, какой конкретный класс создается. Подклассы Creatorмогут переопределить, какой класс создать. В этом примере Creator1подкласс реализует абстрактное factoryMethod(), создавая экземпляр Product1класса.

Пример

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

Структура

Новый WikiFactoryMethod.png

Roomявляется базовым классом для конечного продукта ( MagicRoomили OrdinaryRoom). MazeGameобъявляет абстрактный фабричный метод для производства такого базового продукта. MagicRoomи OrdinaryRoomявляются подклассами базового продукта, реализующими конечный продукт. MagicMazeGameи OrdinaryMazeGameявляются подклассами MazeGameреализации заводского метода производства конечных продуктов. Таким образом, фабричные методы отделяют вызывающих ( MazeGame) от реализации конкретных классов. Это делает «нового» оператора избыточным, позволяет придерживаться принципа « открыт / закрыт» и делает конечный продукт более гибким в случае изменения.

Примеры реализации

C #

// 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 Factory
{
    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. В зависимости от типа, переданного в Factoryобъект, мы возвращаем исходный конкретный объект в качестве интерфейса IPerson.

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

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вcreteFactory. В результате вы можете легко позвонить 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 Room makeRoom() {
        return new MagicRoom();
    }
}

public class OrdinaryMazeGame extends MazeGame {
    @Override
    protected Room 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();

Python

То же, что и в примере на 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('Playing using "{}"'.format(self.rooms[0]))

    @abstractmethod
    def make_room(self):
        raise NotImplementedError("You should implement this!")


class MagicMazeGame(MazeGame):
    def make_room(self):
        return MagicRoom()


class OrdinaryMazeGame(MazeGame):
    def make_room(self):
        return OrdinaryRoom()


class Room(ABC):
    def __init__(self) -> None:
        self.connected_rooms = []

    def connect(self, room) -> None:
        self.connected_rooms.append(room)


class MagicRoom(Room):
    def __str__(self):
        return "Magic room"


class OrdinaryRoom(Room):
    def __str__(self):
        return "Ordinary room"


ordinaryGame = OrdinaryMazeGame()
ordinaryGame.play()

magicGame = MagicMazeGame()
magicGame.play()

Использует

  • В ADO.NET , IDbCommand.CreateParameter является примером использования фабричного метода для подключения параллельных иерархий классов.
  • В Qt , QMainWindow :: CreatePopupMenu является фабричный метод , объявленный в рамках , которые могут быть переопределены в коде приложения .
  • В Java несколько фабрик используются в пакете javax.xml.parsers . например javax.xml.parsers.DocumentBuilderFactory или javax.xml.parsers.SAXParserFactory.
  • В HTML5 DOM API интерфейс Document содержит фабричный метод createElement для создания определенных элементов интерфейса HTMLElement.

Смотрите также

использованная литература

внешние ссылки