Google Geminiソフトウェア開発⭐ リポ 30品質スコア 79/100
Design Patterns GoF — Delphi
Object Pascal/Delphiで、インターフェース、TInterfacedObject、およびSOLID原則を活用した23のGoF(Gang of Four)デザインパターンの実装です。生成系、構造系、振る舞い系のパターンをカバーしています。
description の原文を見る
Implementation of the 23 GoF (Gang of Four) patterns in Object Pascal / Delphi with interfaces, TInterfacedObject and SOLID principles. Covers Creational, Structural and Behavioral patterns.
SKILL.md 本文
Delphi における Design Patterns GoF — スキル
ユーザーが Delphi でのデザインパターンの実装をリクエストする場合は、このスキルを使用してください。常に命名規約 (T/I/E/F/A/L) とメモリ管理 (try..finally) と組み合わせて適用します。
いつ使用するか
- 複雑なオブジェクトの作成に
Factory、Abstract Factory、Builderを使用する - アルゴリズムの変動に
Strategyを実装する (運送料金計算、税金計算、エクスポート) - 結合度の低い通知に
Observerを使用する (ドメインイベント) - アンドゥ/リドゥ、ジョブキュー、監査に
Commandを適用する - 継承なしで責任を追加する場合に
Decoratorを使用する - レガシーシステムとの統合に
Adapterを実装する - 複雑なサブシステムを単純化するために
Facadeを使用する (例: 電子インボイス発行) - アルゴリズムの変動に
Template Methodを適用する (レポート、エクスポート) - オブジェクトの状態に応じて変わる振る舞いに
Stateを使用する - 検証またはプロセッシングパイプラインに
Chain of Responsibilityを使用する
🏗️ 生成パターン
Singleton — グローバル設定
unit MeuApp.Infra.AppConfig;
interface
type
TAppConfig = class
private
class var FInstance: TAppConfig;
FDatabaseUrl: string;
FApiKey: string;
constructor Create;
public
class function GetInstance: TAppConfig;
class procedure ReleaseInstance;
property DatabaseUrl: string read FDatabaseUrl write FDatabaseUrl;
property ApiKey: string read FApiKey write FApiKey;
end;
implementation
constructor TAppConfig.Create;
begin
inherited Create;
FDatabaseUrl := 'localhost:5432/myapp';
end;
class function TAppConfig.GetInstance: TAppConfig;
begin
if not Assigned(FInstance) then
FInstance := TAppConfig.Create;
Result := FInstance;
end;
class procedure TAppConfig.ReleaseInstance;
begin
FreeAndNil(FInstance);
end;
initialization
finalization
TAppConfig.ReleaseInstance;
end.
Factory Method — ポリモーフィズムによる生成
unit MeuApp.Domain.Report.Factory;
interface
uses
MeuApp.Domain.Report.Intf;
type
IReportExporter = interface
['{A1B2-...}']
procedure Export(const AData: TReportData; const AFilePath: string);
end;
// Factory Method — 各サブクラスが作成するエクスポーターを決定
TReportExporterFactory = class abstract
public
function CreateExporter: IReportExporter; virtual; abstract;
// Template Method が Factory Method を使用
procedure ExportReport(const AData: TReportData; const AFilePath: string);
end;
TPdfReportFactory = class(TReportExporterFactory)
function CreateExporter: IReportExporter; override;
end;
TExcelReportFactory = class(TReportExporterFactory)
function CreateExporter: IReportExporter; override;
end;
implementation
procedure TReportExporterFactory.ExportReport(const AData: TReportData; const AFilePath: string);
var
LExporter: IReportExporter;
begin
LExporter := CreateExporter;
LExporter.Export(AData, AFilePath);
end;
Abstract Factory — 関連オブジェクトのファミリー
unit MeuApp.Infra.UI.Factory;
interface
type
IButton = interface ['{...}'] procedure Render; end;
IInputField = interface ['{...}'] procedure Render; end;
IDialog = interface ['{...}'] procedure Show(const AMsg: string); end;
// Abstract Factory
IUIComponentFactory = interface
['{B2C3-...}']
function CreateButton(const ACaption: string): IButton;
function CreateInputField(const APlaceholder: string): IInputField;
function CreateDialog: IDialog;
end;
TVCLComponentFactory = class(TInterfacedObject, IUIComponentFactory)
function CreateButton(const ACaption: string): IButton;
function CreateInputField(const APlaceholder: string): IInputField;
function CreateDialog: IDialog;
end;
TFMXComponentFactory = class(TInterfacedObject, IUIComponentFactory)
function CreateButton(const ACaption: string): IButton;
function CreateInputField(const APlaceholder: string): IInputField;
function CreateDialog: IDialog;
end;
Builder — ステップバイステップの構築
unit MeuApp.Infra.Query.Builder;
interface
uses
System.SysUtils,
System.Classes,
System.Generics.Collections;
type
/// <summary>
/// パラメータ化された SQL クエリのフルエント構築用ビルダー。
/// </summary>
TQueryBuilder = class
private
FSelect: string;
FFrom: string;
FWheres: TStringList;
FOrderBy: string;
FLimit: Integer;
public
constructor Create;
destructor Destroy; override;
function Select(const AFields: string): TQueryBuilder;
function From(const ATable: string): TQueryBuilder;
function Where(const ACondition: string): TQueryBuilder;
function OrderBy(const AField: string; ADesc: Boolean = False): TQueryBuilder;
function Limit(ACount: Integer): TQueryBuilder;
function Build: string;
end;
implementation
constructor TQueryBuilder.Create;
begin
inherited Create;
FWheres := TStringList.Create;
FLimit := 0;
end;
destructor TQueryBuilder.Destroy;
begin
FWheres.Free;
inherited Destroy;
end;
function TQueryBuilder.Select(const AFields: string): TQueryBuilder;
begin
FSelect := AFields;
Result := Self;
end;
function TQueryBuilder.From(const ATable: string): TQueryBuilder;
begin
FFrom := ATable;
Result := Self;
end;
function TQueryBuilder.Where(const ACondition: string): TQueryBuilder;
begin
FWheres.Add(ACondition);
Result := Self;
end;
function TQueryBuilder.OrderBy(const AField: string; ADesc: Boolean): TQueryBuilder;
begin
FOrderBy := AField;
if ADesc then FOrderBy := FOrderBy + ' DESC';
Result := Self;
end;
function TQueryBuilder.Limit(ACount: Integer): TQueryBuilder;
begin
FLimit := ACount;
Result := Self;
end;
function TQueryBuilder.Build: string;
var
LSql: TStringBuilder;
begin
LSql := TStringBuilder.Create;
try
LSql.Append('SELECT ').Append(FSelect);
LSql.Append(' FROM ').Append(FFrom);
if FWheres.Count > 0 then
LSql.Append(' WHERE ').Append(String.Join(' AND ', FWheres.ToStringArray));
if not FOrderBy.IsEmpty then
LSql.Append(' ORDER BY ').Append(FOrderBy);
if FLimit > 0 then
LSql.Append(' LIMIT ').Append(FLimit.ToString);
Result := LSql.ToString;
finally
LSql.Free;
end;
end;
🔧 構造パターン
Adapter — レガシー統合
unit MeuApp.Infra.Payment.Adapter;
interface
type
// レガシーシステム — 変更できない
TLegacyPaymentGateway = class
procedure ProcessarPagamento(AValor: Double; ACodigoCartao: string);
end;
// モダンドメインが期待するインターフェース
IPaymentGateway = interface
['{C3D4-...}']
procedure ProcessPayment(AAmount: Currency; const ACardToken: string);
end;
// アダプター: 新しい呼び出しをレガシーに翻訳
TLegacyPaymentAdapter = class(TInterfacedObject, IPaymentGateway)
private
FLegacy: TLegacyPaymentGateway;
public
constructor Create(ALegacy: TLegacyPaymentGateway);
destructor Destroy; override;
procedure ProcessPayment(AAmount: Currency; const ACardToken: string);
end;
implementation
constructor TLegacyPaymentAdapter.Create(ALegacy: TLegacyPaymentGateway);
begin
inherited Create;
if not Assigned(ALegacy) then
raise EArgumentNilException.Create('ALegacy gateway cannot be nil');
FLegacy := ALegacy;
end;
destructor TLegacyPaymentAdapter.Destroy;
begin
FLegacy.Free; // アダプターはレガシーを所有
inherited Destroy;
end;
procedure TLegacyPaymentAdapter.ProcessPayment(AAmount: Currency; const ACardToken: string);
begin
FLegacy.ProcessarPagamento(AAmount, ACardToken);
end;
Decorator — 継承なしの拡張
unit MeuApp.Infra.Logger.Decorators;
interface
type
ILogger = interface
['{D4E5-...}']
procedure Log(const ALevel, AMessage: string);
end;
TConsoleLogger = class(TInterfacedObject, ILogger)
procedure Log(const ALevel, AMessage: string);
end;
// デコレーター: タイムスタンプを追加
TTimestampDecorator = class(TInterfacedObject, ILogger)
private
FInner: ILogger;
public
constructor Create(AInner: ILogger);
procedure Log(const ALevel, AMessage: string);
end;
// デコレーター: 最小レベルでフィルタリング
TLevelFilterDecorator = class(TInterfacedObject, ILogger)
private
FInner: ILogger;
FMinLevel: string;
public
constructor Create(AInner: ILogger; const AMinLevel: string);
procedure Log(const ALevel, AMessage: string);
end;
implementation
procedure TConsoleLogger.Log(const ALevel, AMessage: string);
begin
Writeln(Format('[%s] %s', [ALevel, AMessage]));
end;
procedure TTimestampDecorator.Log(const ALevel, AMessage: string);
begin
FInner.Log(ALevel, Format('%s | %s', [FormatDateTime('yyyy-mm-dd hh:nn:ss', Now), AMessage]));
end;
procedure TLevelFilterDecorator.Log(const ALevel, AMessage: string);
begin
if ALevel >= FMinLevel then // 本番環境で DEBUG をフィルタリング
FInner.Log(ALevel, AMessage);
end;
Facade — サブシステムの単純化
unit MeuApp.Application.NFe.Facade;
interface
uses
MeuApp.Domain.NFe.Entity,
MeuApp.Domain.NFe.Repository.Intf;
type
/// <summary>
/// 電子インボイス (NF-e) 発行の全過程を単純化するファサード。
/// XML、デジタル署名、SEFAZ との通信を隠蔽します。
/// </summary>
TNFeFacade = class
private
FXmlBuilder: TNFeXmlBuilder;
FSigner: TDigitalSigner;
FTransmitter: TSefazTransmitter;
FRepository: INFeRepository;
procedure GenerateXml(ANFe: TNFe);
procedure SignXml(ANFe: TNFe);
procedure TransmitToSefaz(ANFe: TNFe);
procedure PersistResult(ANFe: TNFe);
public
constructor Create(ARepository: INFeRepository);
destructor Destroy; override;
/// <summary>NF-e を発行: XML 生成 → 署名 → 送信 → 永続化。</summary>
procedure EmitirNFe(ANFe: TNFe);
/// <summary>既に発行された NF-e をキャンセル。</summary>
procedure CancelarNFe(const AChaveAcesso: string; const AMotivo: string);
end;
Proxy — アクセス制御
unit MeuApp.Infra.Customer.Repository.Proxy;
interface
uses
MeuApp.Domain.Customer.Entity,
MeuApp.Domain.Customer.Repository.Intf,
MeuApp.Domain.User.Entity;
type
/// <summary>
/// 実リポジトリへ委譲する前に権限をチェックするセキュリティプロキシ。
/// </summary>
TSecureCustomerRepositoryProxy = class(TInterfacedObject, ICustomerRepository)
private
FReal: ICustomerRepository;
FCurrentUser: TUser;
procedure CheckPermission(const AAction: string);
public
constructor Create(AReal: ICustomerRepository; ACurrentUser: TUser);
function FindById(AId: Integer): TCustomer;
function FindAll: TObjectList<TCustomer>;
procedure Insert(ACustomer: TCustomer);
procedure Update(ACustomer: TCustomer);
procedure Delete(AId: Integer);
end;
implementation
procedure TSecureCustomerRepositoryProxy.CheckPermission(const AAction: string);
begin
if not FCurrentUser.HasPermission(AAction) then
raise EAuthorizationException.CreateFmt(
'User "%s" does not have permission: %s', [FCurrentUser.Login, AAction]);
end;
procedure TSecureCustomerRepositoryProxy.Delete(AId: Integer);
begin
CheckPermission('CUSTOMER_DELETE');
FReal.Delete(AId);
end;
🎭 振る舞いパターン
Strategy — アルゴリズムの変動
unit MeuApp.Domain.Tax.Strategies;
interface
type
ITaxStrategy = interface
['{E5F6-...}']
function Calculate(ABaseValue: Currency): Currency;
function GetDescription: string;
end;
TSimplesTaxStrategy = class(TInterfacedObject, ITaxStrategy)
public
function Calculate(ABaseValue: Currency): Currency;
function GetDescription: string;
end;
TLucroPresumidoStrategy = class(TInterfacedObject, ITaxStrategy)
public
function Calculate(ABaseValue: Currency): Currency;
function GetDescription: string;
end;
TIsencaoStrategy = class(TInterfacedObject, ITaxStrategy)
public
function Calculate(ABaseValue: Currency): Currency;
function GetDescription: string;
end;
implementation
const
SIMPLES_RATE = 0.06;
LUCRO_PRESUMIDO_RATE = 0.15;
function TSimplesTaxStrategy.Calculate(ABaseValue: Currency): Currency;
begin
Result := ABaseValue * SIMPLES_RATE;
end;
function TSimplesTaxStrategy.GetDescription: string;
begin
Result := 'Simples Nacional (6%)';
end;
function TIsencaoStrategy.Calculate(ABaseValue: Currency): Currency;
begin
Result := 0;
end;
function TIsencaoStrategy.GetDescription: string;
begin
Result := 'Isento de impostos';
end;
Observer — ドメインイベント
unit MeuApp.Domain.Order.Events;
interface
uses
System.Generics.Collections,
MeuApp.Domain.Order.Entity;
type
IOrderEventObserver = interface
['{F6A7-...}']
procedure OnOrderPlaced(AOrder: TOrder);
procedure OnOrderCancelled(AOrder: TOrder);
end;
TOrderEventPublisher = class
private
FObservers: TList<IOrderEventObserver>;
public
constructor Create;
destructor Destroy; override;
procedure Subscribe(AObserver: IOrderEventObserver);
procedure Unsubscribe(AObserver: IOrderEventObserver);
procedure NotifyOrderPlaced(AOrder: TOrder);
procedure NotifyOrderCancelled(AOrder: TOrder);
end;
// 具体的なオブザーバー
TEmailOrderNotifier = class(TInterfacedObject, IOrderEventObserver)
procedure OnOrderPlaced(AOrder: TOrder); // メールで確認を送信
procedure OnOrderCancelled(AOrder: TOrder); // キャンセル通知を送信
end;
TStockReservationObserver = class(TInterfacedObject, IOrderEventObserver)
procedure OnOrderPlaced(AOrder: TOrder); // 在庫を予約
procedure OnOrderCancelled(AOrder: TOrder); // 在庫を解放
end;
implementation
constructor TOrderEventPublisher.Create;
begin
inherited Create;
FObservers := TList<IOrderEventObserver>.Create;
end;
destructor TOrderEventPublisher.Destroy;
begin
FObservers.Free;
inherited Destroy;
end;
procedure TOrderEventPublisher.NotifyOrderPlaced(AOrder: TOrder);
var
LObserver: IOrderEventObserver;
begin
for LObserver in FObservers do
LObserver.OnOrderPlaced(AOrder);
end;
Command — アクションのラッピング
unit MeuApp.Application.Commands;
interface
uses
MeuApp.Domain.Customer.Entity,
MeuApp.Domain.Customer.Repository.Intf;
type
ICommand = interface
['{A7B8-...}']
procedure Execute;
procedure Undo;
function GetDescription: string;
end;
TCreateCustomerCommand = class(TInterfacedObject, ICommand)
private
FRepository: ICustomerRepository;
FCustomerData: TCustomerDTO;
FCreatedId: Integer;
public
constructor Create(ARepository: ICustomerRepository; AData: TCustomerDTO);
procedure Execute;
procedure Undo;
function GetDescription: string;
end;
TDeleteCustomerCommand = class(TInterfacedObject, ICommand)
private
FRepository: ICustomerRepository;
FCustomerId: Integer;
FBackup: TCustomer;
public
constructor Create(ARepository: ICustomerRepository; ACustomerId: Integer);
destructor Destroy; override;
procedure Execute;
procedure Undo;
function GetDescription: string;
end;
// コマンド履歴 (アンドゥ/リドゥ用)
TCommandHistory = class
private
FHistory: TStack<ICommand>;
FRedoStack: TStack<ICommand>;
public
constructor Create;
destructor Destroy; override;
procedure Execute(ACommand: ICommand);
procedure Undo;
procedure Redo;
function CanUndo: Boolean;
function CanRedo: Boolean;
end;
Template Method — アルゴリズムスケルトン
unit MeuApp.Application.Report.Generator;
interface
type
TReportData = record
Title: string;
StartDate: TDate;
EndDate: TDate;
end;
/// <summary>
/// レポート生成アルゴリズムのスケルトンを定義する抽象基底クラス。
/// サブクラスは可変ステップを実装します。
/// </summary>
TReportGenerator = class abstract
protected
FData: TReportData;
procedure LoadData; virtual; abstract;
procedure ValidateData; virtual; // デフォルト実装を持つフック
procedure ProcessData; virtual; abstract;
procedure FormatOutput; virtual; abstract;
procedure SaveOutput(const APath: string); virtual; abstract;
procedure SendNotification; virtual; // オプションフック — デフォルト: 処理なし
public
// Template Method — オーバーライドできない (final)
procedure Generate(AData: TReportData; const ASavePath: string);
end;
TSalesReportGenerator = class(TReportGenerator)
protected
procedure LoadData; override;
procedure ProcessData; override;
procedure FormatOutput; override;
procedure SaveOutput(const APath: string); override;
procedure SendNotification; override;
end;
implementation
procedure TReportGenerator.Generate(AData: TReportData; const ASavePath: string);
begin
FData := AData;
LoadData;
ValidateData;
ProcessData;
FormatOutput;
SaveOutput(ASavePath);
SendNotification;
end;
procedure TReportGenerator.ValidateData;
begin
if FData.Title.Trim.IsEmpty then
raise EValidationException.Create('Report title cannot be empty');
if FData.StartDate > FData.EndDate then
raise EValidationException.Create('StartDate must be before EndDate');
end;
procedure TReportGenerator.SendNotification;
begin
// デフォルトフック: 空。サブクラスはメール送信などでオーバーライド可能。
end;
Chain of Responsibility — 検証パイプライン
unit MeuApp.Application.Validation.Chain;
interface
uses
MeuApp.Domain.Customer.Entity;
type
TValidationResult = record
IsValid: Boolean;
ErrorMessage: string;
class function Ok: TValidationResult; static;
class function Fail(const AMessage: string): TValidationResult; static;
end;
IValidationHandler = interface
['{B8C9-...}']
procedure SetNext(AHandler: IValidationHandler);
function Validate(ACustomer: TCustomer): TValidationResult;
end;
TBaseValidationHandler = class(TInterfacedObject, IValidationHandler)
private
FNext: IValidationHandler;
public
procedure SetNext(AHandler: IValidationHandler);
function Validate(ACustomer: TCustomer): TValidationResult; virtual;
end;
TNameValidationHandler = class(TBaseValidationHandler)
function Validate(ACustomer: TCustomer): TValidationResult; override;
end;
TCpfValidationHandler = class(TBaseValidationHandler)
function Validate(ACustomer: TCustomer): TValidationResult; override;
end;
TEmailValidationHandler = class(TBaseValidationHandler)
function Validate(ACustomer: TCustomer): TValidationResult; override;
end;
implementation
function TBaseValidationHandler.Validate(ACustomer: TCustomer): TValidationResult;
begin
// 次のハンドラーへ委譲 (存在する場合)
if Assigned(FNext) then
Result := FNext.Validate(ACustomer)
else
Result := TValidationResult.Ok;
end;
function TNameValidationHandler.Validate(ACustomer: TCustomer): TValidationResult;
begin
if ACustomer.Name.Trim.IsEmpty then
Exit(TValidationResult.Fail('Nome é obrigatório'));
if ACustomer.Name.Length < 3 then
Exit(TValidationResult.Fail('Nome deve ter ao menos 3 caracteres'));
Result := inherited Validate(ACustomer); // チェーンを継続
end;
State — 状態による振る舞い
unit MeuApp.Domain.Order.States;
interface
uses
MeuApp.Domain.Order.Entity;
type
IOrderState = interface
['{C9D0-...}']
procedure Confirm(AOrder: TOrder);
procedure Ship(AOrder: TOrder);
procedure Deliver(AOrder: TOrder);
procedure Cancel(AOrder: TOrder);
function GetStatusDescription: string;
end;
// 状態: 新規 (確認待ち)
TNewOrderState = class(TInterfacedObject, IOrderState)
public
procedure Confirm(AOrder: TOrder);
procedure Ship(AOrder: TOrder); // EInvalidOperationException を発生
procedure Deliver(AOrder: TOrder);
procedure Cancel(AOrder: TOrder);
function GetStatusDescription: string;
end;
// 状態: 確認済み (送付待ち)
TConfirmedOrderState = class(TInterfacedObject, IOrderState)
public
procedure Confirm(AOrder: TOrder); // EInvalidOperationException を発生
procedure Ship(AOrder: TOrder);
procedure Deliver(AOrder: TOrder);
procedure Cancel(AOrder: TOrder);
function GetStatusDescription: string;
end;
// 状態: 発送中
TShippedOrderState = class(TInterfacedObject, IOrderState)
public
procedure Confirm(AOrder: TOrder);
procedure Ship(AOrder: TOrder);
procedure Deliver(AOrder: TOrder);
procedure Cancel(AOrder: TOrder); // 配送業者への連絡が必要
function GetStatusDescription: string;
end;
implementation
procedure TNewOrderState.Confirm(AOrder: TOrder);
begin
AOrder.ConfirmedAt := Now;
AOrder.SetState(TConfirmedOrderState.Create); // 状態遷移
end;
procedure TNewOrderState.Ship(AOrder: TOrder);
begin
raise EInvalidOperationException.Create('Pedido ainda não confirmado. Confirme antes de enviar.');
end;
function TNewOrderState.GetStatusDescription: string;
begin
Result := 'Novo — aguardando confirmação';
end;
📌 適切なパターンを選択するガイド
| 必要な機能 | 標準パターン |
|---|---|
| 型の変動でオブジェクトを作成 | Factory Method / Abstract Factory |
| 複雑なオブジェクトをステップバイステップで作成 | Builder |
| グローバルな単一インスタンスを保証 | Singleton |
| オブジェクトをコピー | Prototype |
| 互換性のないインターフェースに対応 | Adapter |
| 継承なしで動的に責任を追加 | Decorator |
| 複雑なシステムを単純化 | Facade |
| オブジェクトへのアクセスを制御 | Proxy |
| ツリー構造でオブジェクトを構成 | Composite |
| コンテキストを変更せずアルゴリズムを変動 | Strategy |
| 複数のオブジェクトに変更を通知 | Observer |
| アンドゥ/リドゥのアクションをカプセル化 | Command |
| 変動を伴うアルゴリズム | Template Method |
| ハンドラーチェーンでリクエストを渡す | Chain of Responsibility |
| 状態に応じて変わる振る舞い | State |
✅ 最終チェックリスト
- すべての依存関係がコンストラクタ経由で注入されている (DIP)
- すべてのインターフェース実装が
TInterfacedObjectを使用している (ARC) -
.Createなしでtry..finallyがない (ARC インターフェース以外) - 各クラスが単一の責任を持っている (SRP)
- DUnitX テストが Fakes/Stubs で各パターンをカバーしている
- 公開メソッドに XMLDoc がポルトガル語で記載されている
- プレフィックス: クラスは
T、インターフェースはI、例外はE、フィールドはF、パラメータはA、ローカル変数はL
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- delphicleancode
- ライセンス
- MIT
- 最終更新
- 2026/3/25
Source: https://github.com/delphicleancode/delphi-spec-kit / ライセンス: MIT