Примеры порождающих паттернов из Банды четырёх (GoF) на Delphi

Вольный перевод статьи Gang-of-Four Creational Design Pattern Examples in Delphiopen in new window

Специализированная для Delphi версия Порождающих паттернов из книги “Design Patterns: Elements of Reusable Object-Oriented Software”.

Абстрактная фабрика (Abstract Factory)

Абстрактная фабрика это класс который создаёт компонент и возвращает его в виде базового (абстрактного) типа. Потребитель (код который использует фабрику) не видит реализацию ни фабрики, ни объектов которые она производит, а работает только с базовыми типами.

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

unit IntfMazeFactory;

interface
uses
  system.generics.collections;

type

IMaze = interface
 // Определение не важно для примера
end;

IWall = interface
 // Определение не важно для примера
end;

IRoom = interface
 // Определение не важно для примера
end;

IDoor = interface
 // Определение не важно для примера
end;

IMazeFactory = interface
  function MakeMaze: IMaze;
  function MakeWall: IWall;
  function MakeRoom(ANumber: integer): TArray<IRoom>;
  function MakeDoor(AFromRoom, AToRoom: IRoom): IDoor;  
end;

// Мы можем использовать фабрику как параметр, например
//   MazeGame.CreateMaze(AFactory);
// MazeGame может создать лабиринт не привязываясь 
// к конкретной реализации фабрики или тем более 
// к конкретным реализациям объектов которые она производит

implementation

end.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

Сборщик (Builder)

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

unit IntfMazeBuilder;

interface

type

IMazeBuilder = interface
  procedure BuildMaze;
  procedure BuildRoom(ANumber: integer);
  procedure BuildDoor(AFromRoomIndex, AToRoomIndex: integer); 

  function GetMaze: IMaze; 
end;

// Сборщик также можно передавать в виде параметра 
//   LMaze := MazeGame.CreateMaze(ABuilder);
// MazeGame также как и с фабрикой не привязывается к конкретной 
// реализации объектов лабиринта. Отличия от фабрики в том что
// потребитель (MazeGame) может даже не знать о 
// блоках, порядке и связях между частями лабиринта

implementation

end.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

Фабричный метод (Factory Method)

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

unit IntfFactoryMethod;

interface

type

IProduct = interface
 // Определение не важно для примера
end;

TAbstractProductCreator = class abstract
public
  function CreateProduct(AProductID: integer): IProduct; virtual;  
end;

// Шаблон проектирования действительно строится на основе одного метода.
// Метод которые создаёт продукты должен быть виртуальным 
// и может быть переопределён потомками.
// В методе может быть заложена логика для создания некоторых видов объектов по умолчанию. 
// В дальнейшем эта логика расширяется в потомках. 
// Обработка вызова, при создании методом объекта, идёт обычным путём 
// по иерархии наследования от потомков к предкам.

implementation

uses    
    ProductTags; // Предполагаем что идентификаторы продуктов определены здесь

Type

TBaseProductA = class(TInterfacedObnject, IProduct)
 // Определение не важно для примера
end;

TBaseProductB = class(TInterfacedObnject, IProduct)
 // Определение не важно для примера
end;

function TAbstractProductCreator.CreateProduct(AProductID: integer): IProduct;
begin
    // Потомки должны добавлять свои случаи и проваливаться в наследуемый метод
   Case AProductID of 
     ProductTags.BaseProductA:
        result := TBaseProductA.Create;
     ProductTags.BaseProductB:
        result := TBaseProductB.Create;
    else
        raise EProductIDUnkownException.Create;
   end;
end;

end.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

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

unit ConcreateFactoryMethod;

interface
uses
    intfFactoryMethod;

type

TConcreteProductCreator = class(TAbstractProductCreator)
public
  function CreateProduct(AProductID: integer): IProduct; override;  
end;

// Фабричный метод, создающий продукты, виртуальный и может быть переопределён 

implementation

uses    
    ProductTags; // Предполагаем что идентификаторы продуктов определены здесь

Type

TAdvancedProductX = class(TInterfacedObnject, IProduct)
 // Определение не важно для примера
end;

TAdvancedProductY = class(TProductA)
 // Определение не важно для примера
end;

function TConcreteProductCreator.CreateProduct(AProductID: integer): IProduct;
begin
   Case AProductID of

     ProductTags.AdvancedProductX:
        result := TAdvancedProductX.Create;
     ProdcutTags.BaseProductA,  ProductTags.AdvancedProductY: // Мы можем скрыть некоторые виды продуктов или перезаписать их
        result := TAdvancedProductY.Create;
    else
      result := inherited;
   end;
end;

end.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

Одиночка (Singleton)

Я редко реализую этот паттерн и предпочитаю чистый абстрактный класс с классовыми свойствами и методами. Этот паттерн особенно сложно реализуется в Delphi потому, что всегда есть конструктор унаследованный от TObject. Так что не получается реализовать паттерн, так как описано в книге. Единственный путь - спрятать конструктор и предоставлять интерфейс вместо класса.

unit SingletonMazeFactory;

interface
uses
  IntfMazeFactory;

Function MazeFactoryInstance: IMazeFactory;

implementation

type

TMazeFactory = class(TInterfacedObject, IMazeFactory)
private 

private
  class var FInstance: IMazeFactory; 

  class function GetInstance: IMazeFactory; static;
public
  Constructor Create;

  function MakeMaze: IMaze;
  function MakeWall: IWall;
  function MakeRoom(ANumber: integer): TArray<IRoom>;
  function MakeDoor(AFromRoom, AToRoom: IRoom): IDoor;  

  class property Instance: IMazeFactory read GetInstance;
end;

Function MazeFactoryInstance: IMazeFactory
begin
  result := TMazeFactory.Instance;
end;

class function TMazeFactory.GetInstance: IMazeFactory;
begin
  if FInstance = nil then
    FInstance := TMazeFactory.Create;
  result := FInstance; 
end;

function TMazeFactory.MakeMaze: IMaze;
begin
   // Определение не важно для примера
end;

constructor TMazeFactory.Create;
begin
  inherited Create;
end;

function TMazeFactory.MakeWall: IWall;
begin
   // Определение не важно для примера
end;

function TMazeFactory.MakeRoom(ANumber: integer): TArray<IRoom>;
begin
   // Определение не важно для примера
end;

function TMazeFactory.MakeDoor(AFromRoom: IRoom; AToRoom: IRoom): IDoor;
begin
   // Определение не важно для примера
end;

end.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

Прототип (Prototype)

Это мой любимый паттерн. Для его использования передавайте экземпляры прототипов в конструктор. Затем используйте их для создания клонов в фабрике. Такой подход предоставляет возможности для расширения, когда у вас нет доступа до кода (похоже на плагины или расширения определяемые пользователем).

unit IntfMazeFactory;

interface
uses
  system.generics.collections;

type

IMaze = interface
  function Clone: IMaze;

 // Остальное определение не важно для примера
end;

IWall = interface
  function Clone: IWall;
 // Остальное определение не важно для примера
end;

IRoom = interface
  function Clone: IRoom;

 // Остальное определение не важно для примера
end;

IDoor = interface
  function Clone: IDoor;
  procedure Initialize(AFromRoom, AToRoom: IRoom); //mutator
 // Остальное определение не важно для примера
end;

TMazePrototypeFactory = Class 
private
  FProtoMaze: IMaze; 
  FProtoWall: IWall; 
  FProtoRoom: IRoom; 
  FProtoDoor: IDoor;
public
  constructor Create(AMaze: IMaze; AWall: IWall; ARoom: IRoom; ADoor: IDoor);

  function MakeMaze: IMaze;
  function MakeWall: IWall;
  function MakeRoom(ANumber: integer): TArray<IRoom>;
  function MakeDoor(AFromRoom, AToRoom: IRoom): IDoor;  
end;

// Мы можем использовать фабрику как параметр
//   MazeGame.CreateMaze(AFactory);

implementation

constructor TMazePrototypeFactory.Create(AMaze: IMaze; AWall: IWall; ARoom: IRoom; ADoor: IDoor);
begin
  inherited Create;
  FProtoMaze := AMaze; 
  FProtoWall := AWall; 
  FProtoRoom := ARoom; 
  FProtoDoor := ADoor;
end;

function TMazePrototypeFactory.MakeMaze: IMaze;
begin
  result := FProtoMaze.Clone;
end;

function TMazePrototypeFactory.MakeWall: IWall;
begin
  result := FProtoWall.Clone;
end;

function TMazePrototypeFactory.MakeRoom(ANumber: integer): TArray<IRoom>;
begin
  for i := 1 to ANumber do
    result.Add(FProtoRoom.Clone);
end;

function TMazePrototypeFactory.MakeDoor(AFromRoom: IRoom; AToRoom: IRoom): IDoor;
begin
  result := ARoom.Clone;
  result.Initialize(AFromRoom, AToRoom);
end;

end.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
Последниее изменение: 24.08.2023, 06:42:55