Примеры порождающих паттернов из Банды четырёх (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.
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.
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.
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.
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.
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.
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