Краткое изложение шаблонов проектирования GoF

Намерение

Предоставьте интерфейс для создания семейств связанных или зависимых объектов без указания их конкретного класса.

Также известен как

Набор

Мотивация

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

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

Для каждого стандарта внешнего вида существует конкретный подкласс WidgetFactory. Клиент создает виджеты исключительно через интерфейс WidgetFactory и ничего не знает о классах, которые реализуют виджет для определенного внешнего вида.

WidgetFactory также устанавливает зависимости между конкретными классами виджетов. Фиктивную полосу прокрутки следует использовать с фиктивной кнопкой и фиктивным текстовым редактором, и это ограничение применяется автоматически вследствие использования DummyWidgetFactory.

Применимость

Используйте шаблон «Абстрактная фабрика», когда

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

Состав

Участники

Абстрактная фабрика

(Фабрика виджетов)

  • объявляет интерфейс для операций, которые создают абстрактные объекты продукта.

Бетонный завод

(DummyWidgetFactory, TWidgetFactory)

  • реализует операции по созданию конкретных объектов-продуктов.

Абстрактный продукт

(окно, полоса прокрутки)

  • объявляет интерфейс для типа объекта продукта.

БетонПродукт

(DummyWindow, DummyScrollBar)

  • определяет объект продукта, который должен быть создан соответствующей конкретной фабрикой.
  • реализует интерфейс AbstractProduct.

Клиент

  • использует только интерфейсы, объявленные классами AbstractFactory и AbstractProduct.

Сотрудничество

  • Обычно один экземпляр класса ConcreteFactory создается во время выполнения. Эта конкретная фабрика создает объекты продукта, имеющие конкретную реализацию. Для создания различных объектов продукта клиенты должны использовать другую конкретную фабрику.
  • AbstractFactory передает создание объектов продукта своему подклассу ConcreteFactory.

Последствия

Шаблон «Абстрактная фабрика» имеет следующие преимущества и недостатки:

  1. Изолирует конкретные классы. Абстрактный шаблон Factory помогает вам управлять классами объектов, которые создает приложение. Клиенты управляют экземплярами через свои абстрактные интерфейсы.
  2. Это упрощает обмен семействами продуктов. Класс бетонной фабрики появляется в приложении только один раз, то есть там, где он создается. Это упрощает изменение конкретной фабрики, используемой приложением.
  3. Это способствует согласованности между продуктами. Когда объекты продукта в семействе предназначены для совместной работы, важно, чтобы приложение одновременно использовало объекты только из одного семейства. AbstractFactory позволяет легко реализовать это.
  4. Поддерживать новые виды продуктов сложно. Расширить абстрактные фабрики для производства новых видов Продуктов непросто. Для поддержки новых видов продуктов требуется расширение интерфейса фабрики, что включает в себя изменение класса AbstractFactory и всех его подклассов. (Решение в следующем разделе)

Выполнение

Вот несколько полезных приемов реализации шаблона «Абстрактная фабрика».

  1. Фабрики как одиночки. Приложению обычно требуется только один экземпляр ConcreteFactory для каждого семейства продуктов.
  2. Создание продуктов. AbstractFactory объявляет только интерфейс для создания продуктов. Их фактическое создание зависит от подклассов ConcreteProduct. Наиболее распространенный способ сделать это — определить фабричный метод для каждого продукта. Конкретная фабрика будет определять свои продукты, переопределяя фабричный метод для каждого из них. Хотя эта реализация проста, она требует нового конкретного подкласса фабрики для каждого продукта, даже если семейства продуктов отличаются незначительно. Во многих семействах продуктов возможна конкретная фабрика, которая может быть реализована с использованием шаблона прототипа.
  3. Определение расширяемых фабрик. AbstractFactory обычно определяет разные операции для каждого типа продукта, который она может производить. Добавление нового вида продукта требует изменения интерфейса AbstractFactory и всех классов, которые от него зависят. Более гибкая, но менее безопасная конструкция заключается в добавлении параметра к операциям, создающим объекты. Этот параметр указывает тип создаваемого объекта.

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

Образец кода

Мы применим шаблон Абстрактная фабрика для создания лабиринтов.

Чтобы лучше понять следующий код и используемые классы, посмотрите

вот🔗!

Класс MazeFactory может создавать компоненты лабиринтов. Он строит комнаты, стены и двери между комнатами. Например, его может использовать программа, которая случайным образом строит лабиринты. Программы, строящие лабиринты, принимают MazeFactory в качестве аргумента, чтобы программист мог указать классы комнат, стен и дверей для построения.

import commoncode.*;

public class MazeFactory {

  public MazeFactory() {}

  public Maze MakeMaze() 
    { return new Maze(); }

  public Wall MakeWall() 
    { return new Wall(); }

  public Room MakeRoom(int n) 
    { return new Room(n); }

  public Door MakeDoor(Room r1, Room r2)
    { return new Door(r1, r2); }

}

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

import commoncode.*;

public class MazeGame_Factory {

  public Maze CreateMaze(MazeFactory factory) {
    Maze aMaze   = factory.MakeMaze();
    Room r1      = factory.MakeRoom(1);
    Room r2      = factory.MakeRoom(2);
    Door theDoor = factory.MakeDoor(r1, r2);

    r1.SetSide(Direction.North, factory.MakeWall());
    r1.SetSide(Direction.East,  theDoor);
    r1.SetSide(Direction.South, factory.MakeWall());
    r1.SetSide(Direction.West,  factory.MakeWall());

    r2.SetSide(Direction.North, factory.MakeWall());
    r2.SetSide(Direction.East,  factory.MakeWall());
    r2.SetSide(Direction.South, factory.MakeWall());
    r2.SetSide(Direction.West,  theDoor);

    aMaze.AddRoom(r1);
    aMaze.AddRoom(r2);

    return aMaze;
  }     
  
}

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

Мы можем сделать подкласс Room(здесь 🔗), чтобы отслеживать, есть ли в комнате бомба.

import commoncode.*;

public class RoomWithABomb extends Room {
  
  int bombDamage;

  public RoomWithABomb(int roomNo) {
    super(roomNo);
  }  

  public void setBombDamage(int bombDamage) {
    this.bombDamage = bombDamage;
  } 

}

Нам также нужен подкласс Wall, чтобы отслеживать повреждения.

import commoncode.*;

public class BombedWall extends Wall {
  
  int wallDamage = 0;
  
  public BombedWall() {
    super();
  }  

  public void hitWall(int bombDamage) {
    this.wallDamage = bombDamage;
  }

}

Последний класс, который мы определили, — это BombedMazefactory, подкласс MazeFactory (определенного выше). Этому классу нужно переопределить только две функции:

import commoncode.*;

public class BombedMazeFactory extends MazeFactory {
     
  public Wall MakeWall() 
    { return new BombedWall(); }

  public Room MakeRoom(int n) 
    { return new RoomWithABomb(n); }

}

Чтобы построить простой лабиринт, который может содержать или не содержать бомбу, мы просто вызываем CreateMaze с BombedMazeFactory или MazeFactory.

public class MainAbstractFactory {

  public static void main(String[] args) {
    MazeGame_Factory game = new MazeGame_Factory();

    MazeFactory factory = new MazeFactory();
    BombedMazeFactory bombedFactory = new BombedMazeFactory();

    game.CreateMaze(bombedFactory /* factory */);
  }  
  
}

Обратите внимание, что MazeFactory — это просто набор фабричных методов. Это наиболее распространенный способ реализации шаблона абстрактной фабрики. Также обратите внимание, что MazeFactory не является абстрактным классом; он действует как AbstractFactory и ConcreteFactory.

Известные виды использования

InterViews использует суффикс «Kit» [Lin92] для обозначения классов AbstractFactory. Он определяет абстрактные фабрики WidgetKit и DialogKit для создания объектов пользовательского интерфейса, зависящих от внешнего вида. InterViews также включает в себя LayoutKit, который генерирует различные объекты композиции в зависимости от желаемого макета.

Связанные шаблоны

Классы AbstractFactory часто реализуются с помощью фабричных методов, но они также могут быть реализованы с использованием Prototype.

Бетонная фабрика часто является синглтоном.