Вольный перевод статьи Gang-of-Four Creational Design Pattern Examples in Delphi
Специализированная для Delphi версия Порождающих паттернов из книги “Design Patterns: Elements of Reusable Object-Oriented Software”.
Абстрактная фабрика (Abstract Factory)
Абстрактная фабрика это класс который создаёт компонент и возвращает его в виде базового (абстрактного) типа. Потребитель (код который использует фабрику) не видит реализацию ни фабрики, ни объектов которые она производит, а работает только с базовыми типами.
В этом примере используются интерфейсы вместо абстрактных классов, которые используются в примерах в книге. Любое количество конкретных классов могут реализовывать интерфейс фабрики.
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 |
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. |
Сборщик (Builder)
Сборщик похож на Абстрактную фабрику, но предоставляет потребителю более высокий уровень абстракции. Фабрика предоставляет методы для создания отдельных элементов, а Сборщик собирает готовый продукт. Вид продукта и его состав может определятся через параметры.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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. |
Фабричный метод (Factory Method)
Фабричный метод это виртуальный метод, создающий продукты. Он может быть переопределён для расширения производимого набора классов.
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 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 |
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. |
Одиночка (Singleton)
Я редко реализую этот паттерн и предпочитаю чистый абстрактный класс с классовыми свойствами и методами. Этот паттерн особенно сложно реализуется в Delphi потому, что всегда есть конструктор унаследованный от TObject. Так что не получается реализовать паттерн, так как описано в книге. Единственный путь - спрятать конструктор и предоставлять интерфейс вместо класса.
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 |
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. |
Прототип (Prototype)
Это мой любимый паттерн. Для его использования передавайте экземпляры прототипов в конструктор. Затем используйте их для создания клонов в фабрике. Такой подход предоставляет возможности для расширения, когда у вас нет доступа до кода (похоже на плагины или расширения определяемые пользователем).
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 |
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. |
про Singleton… Можно использовать такой вариант, ничего сложно в реализации:
type
TSingletone = class sealed(TObject)
strict private class var
FSingletone: TSingletone;
class destructor Destroy;
public
class function NewInstance: TObject; override;
public
MyField: string;
end;
{ TSingletone }
class destructor TSingletone.Destroy;
begin
FreeAndNil(FSingletone);
end;
class function TSingletone.NewInstance: TObject;
begin
if FSingletone = nil then
FSingletone := TSingletone(inherited NewInstance);
Result := FSingletone;
end;
и дальше можно сколько угодно использовать
var
ASingletone: TSingletone;
begin
ASingletone := TSingletone.Create;
Спасибо, также интересный вариант. Как я понял, автор хотел скрыть конструктор и именно для этого применяет интерфейс.