الگوهای طراحی
نام: Singleton!
مشکل: می خواهیم از یک کلاس تنها بتوان یک نمونه ایجاد کرد.
زمینه: وقتی که نیازی به بیش از یک نمونه نداریم یا دستیابی کنترل شده می خواهیم.
راه حل:

پیاده سازی:
برای پیاده سازی طراحی فوق در دلفی، یک class method ایجاد می کنیم با نام Instance. درخواست یک نمونه از کلاس از طریق این متد انجام خواهد شد و در داخل این متد، تست می کنیم که اگر قبلا یک نمونه از کلاس ایجاد نشده است، یک نمونه ایجاد کرده و ارجاع به آنرا به عنوان خروجی برگرداند در غیر اینصورت ارجاع به همان نمونه ایجاد شده به عنوان جواب برگشت داده می شود. در پیاده سازی انجام شده، فرض بر این است که می خواهیم از کلاس TSingletonForm یک نمونه بیشتر ایجاد نشود.
TSingletonForm = class(TForm)
…
public
…
class function Instance: TSingletonForm;
end;
implementation
var
SingletonFrm:TSingletonForm;
class function TSingletonForm.Instance: TSingletonForm;
begin
if not assigned(SingletonFrm) then
SingletonFrm := TSingletonForm.Create(Application);
result := SingletonFrm;
end;
همچنین در destructor فرم باید ارجاع SingletonFrm را برابر nil قرار داد و در رویداد OnClose فرم، نمونه ایجاد شده را آزاد کرد.
destructor TSingletonForm.Destroy;
begin
SingletonFrm := nil;
inherited Destroy;
end;
procedure TSingletonForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Release;
end;
2- Tamplate Method
مشکل: ساختار کلی یک الگوریتم را می دانیم ولی برخی از جزئیات آن، بستگی دارد به زیر کلاسی که از این کلاس مشتق می شود. می خواهیم این ساختار کلی را در کلاس بالاتر تعریف کنیم، به گونه ای که زیر کلاس ها تنها نیاز به تعریف جزئیات اختصاصی خود داشته باشند.
راه حل:
پیاده سازی:
در ادامه مثالی از پیاده سازی طراحی فوق در دلفی، آورده خواهد شد. در این مثال، یک کلاس TStream داریم که یک کلاس انتزاعی(abstract) می باشد. این کلاس متدی با نام CopyFrom دارد، که حاوی دو پارامتر می باشد. پارامتر اول مشخص کننده یک استریم است و پارامتر دوم تعداد را مشخص می کند. در این متد نیاز داریم تا از استریم ورودی بخوانیم و در استریم خروجی بنویسیم. خواندن و نوشتن در یک استریم بسته به نوع آن استریم، عملیات متفاوتی را لازم دارد. به عنوان مثال TFileStream باید عملیات خواندن و نوشتن را برای یک فایل انجام دهد و StringStream برای یک رشته. متد CopyFrom را به صورت یک Template Method در کلاس انتزاعی TStream تعریف می کنیم. متد CopyFrom ، برای عمل خواندن از متد Read و برای عمل نوشتن از متد Write استفاده می کند. دو متد انتزاعی برای کلاس TStream با نام های Read و Write تعریف می کنیم که این متدها باید در کلاس های مشتق شده از این کلاس پیاده سازی شوند.
{ TStream abstract class }
TStream = class(TObject)
…
public
// primitive operations to be provided by derived classes
function Read(var Buffer; Count: Longint): Longint; virtual; abstract;
function Write(const Buffer; Count: Longint): Longint; virtual; abstract
// template method
function CopyFrom(Source: TStream; Count: Longint): Longint;
…
end;
// concrete class
TStringStream = class(TStream)
…
public
constructor Create(const AString: string);
// concrete class implementations – varying behavior
function Read(var Buffer; Count: Longint): Longint; override;
function Write(const Buffer; Count: Longint): Longint; override;
…
end;
———
function TStream.CopyFrom(Source: TStream; Count: Longint): Longint;
begin
…
Source.Read(Buffer^, N);
Write(Buffer^, N);
…
end;
———
function TStringStream.Read(var Buffer; Count: Longint): Longint;
begin
…
end;
function TStringStream.Write(const Buffer; Count: Longint): Longint;
begin
…
end;
3- Strategy
مشکل: دسترسی به یکسری عملیات مشابه که پیاده سازی متفاوتی دارند. در Template Method ما الگوریتمی داشتیم که کلیات آن یکسان بود ولی در جزئیات ممکن بود متغیر باشد، در اینجا کل الگوریتم مورد استفاده بسته به کاربردش می تواند متغیر باشد.
راه حل:
با استفاده از Context، کلاس هایی که می خواهند به Strategy دسترسی پیدا کنند، مستقل از اینترفیس الگوریتم بکار برده شده در Strategy خواهند بود، زیرا می توانند از اینترفیس عمومی تری که بوسیله Context فراهم می شود، استفاده کنند. همچنین اینترفیس های Context و Strategy باید بدقت طراحی شوند تا بتوانند کلیه الگوریتم های ممکن را که می خواهیم در کلاس های واقعی(Concrete) پیاده سازی کنیم را در بربگیرد.
پیاده سازی:
به عنوان یک مثال، در نظر بگیرید که می خواهیم، شارژ ماهیانه یک کارت اعتباری را محاسبه کنیم. محاسبه میزان شارژ یک کارت اعتباری بسته به نوع آن کارت، الگوریتم های متفاوتی خواهد داشت. ما یک کلاس انتزاعی با عنوان TFinanzeCharge را ایجاد کنیم که به عنوان یک اینترفیس اجازه می دهد تا دسترسی واحدی به تمام پیاده سازی های ممکن برای کارت های اعتباری مختلف داشته باشیم. این کلاس، حاوی یک متد انتزاعی با عنوان getCharge می باشد که باید در کلاس های واقعی که از این کلاس مشتق می شوند، پیاده سازی شود. دو نوع شارژ برای کارت های اعتباری در نظر می گیریم: TRegularCharge و TPreferredCharge. در داخل این کلاس ها باید متد getCharge پیاده سازی شود.
یک کلاس انتزاعی با نام TChargeContext ایجاد می کنیم که به عنوان اینترفیسی برای کلیه کلاس هایی که می خواهند به TFinanzeCharge و کلاس های مشتق شده از آن دسترسی پیدا کنند عمل می کند. این کلاس حاوی یک متد انتزاعی با عنوان computeCharges می باشد. کلاس دیگری با عنوان TMonthlyCharges تعریف می کنیم که از کلاس TChargeContext مشتق می شود و شارژ ماهیانه را برای انواع کارت های اعتباری محاسبه می کند. پیاده سازی انجام شده در زبان دلفی به صورت زیر می باشد:
// strategy interface (abstract class)
TFinanzeCharge = class
public
// returns monthly finanze charge
function getCharge(const Balance: double): double; virtual; abstract;
end;
// Concrete Strategy
TRegularCharge = class(TFinanzeCharge)
public
function getCharge(const Balance: double): double; override;
end;
// Concrete Strategy
TPreferredCharge = class(TFinanzeCharge)
public
function getCharge(const Balance: double): double; override;
end;
// Context Interface
TChargeContext = class
public
function ComputeCharges(const Balance: double): double; virtual; abstract;
end;
// Concrete Context
TMonthlyCharges = class(TChargeContext)
private
FFinanzeCharge: TFinanzeCharge;
public
// context interface called by client classes
function ComputeCharges(const Balance: double): double; override;
// constructor configures the context object
constructor Create(aFinanzeCharge: TFinanzeCharge); virtual;
destructor Destroy; override;
end;
—
implementation
// TRegularCharge
function TRegularCharge.getCharge(const Balance: double): double;
begin
result := Balance * (REG_RATE / 12);
end;
// TPreferredCharge
function TPreferredCharge.getCharge(const Balance: double): double;
begin
result := Balance * (PREFERRED_RATE / 12);
end;
// Concrete Context
// TMonthlyCharges
constructor TMonthlyCharges.Create(aFinanzeCharge: TFinanzeCharge);
begin
inherited Create;
FFinanzeCharge := aFinanzeCharge;
end;
destructor TMonthlyCharges.Destroy;
begin
FFinanzeCharge.Free;
inherited Destroy;
end;
function TMonthlyCharges.ComputeCharges(const Balance: double): double;
begin
result := FFinanzeCharge.getCharge(Balance);
end;
4- Observer
مشکل: ایجاد یک وابستگی یک به چند بین اشیاء، به گونه ای که وقتی وضعیت یک شئ تغییر کرد، تمام اشیائی که به آن وابسته هستند، به صورت اتوماتیک باخبر شده و وضعیت خود را بروز نمایند.
راه حل:
در این طراحی، دو کلاس انتزاعی داریم که به عنوان اینترفیسی برای مشاهده شونده و مشاهده کننده عمل می کنند. هر مشاهده شونده باید بتواند لیستی از مشاهده کننده ها را در خود نگهداری کند و با استفاده از متد Add بتوان یک مشاهده کننده به آن اضافه کرد و با استفاده از Remove مشاهده کننده ای را از آن حذف کرد. همچنین هر مشاهده شونده باید قادر باشد تا تغییر وضعیت خود را به اطلاع کلیه مشاهده کننده ها برساند که اینکار را از طریق متد Notify انجام خواهد داد. این متد، با استفاده از همان لیست مشاهده کننده ها که نگهداری می شود، متد update هر مشاهده کننده را فراخوانی می کند.
هر مشاهده کننده، باید متدی با نام Update داشته باشد که در آن با استفاده از پارامتر ورودی شئ مشاهده شونده را دریافت کرده و سپس وضعیت آنرا دوباره بدست آورده و وضعیت خود را که وابسته به آن است را بروز نماید.
پیاده سازی:
در این مثال، کلاسی داریم با عنوان TMySubject که این کلاس حاوی یک متغیر داخلی با نام x می باشد. می خواهیم از مقدار این x برای نشان دادن پیشرفت در یک ProgressBar استفاده کنیم. بنابراین یک کلاس مشاهده کننده با عنوان TMyBar ایجاد می کنیم که حاوی یک TProgressBar می باشد. عملیات افزودن این کلاس به عنوان یک مشاهده کننده برای کلاس TMySubject و بروزرسانی آن نیز همانگونه که در بالا توضیح داده شد، پیاده سازی خواهد شد. بخش های اصلی کد مورد نیاز در زیر آمده است:
TObserver = class
procedure Update(ChangedSubject: TObservable); virtual; abstract;
end;
// Subject interface
TObservable = class
procedure Add(Observer: TObserver); virtual; abstract;
procedure Remove(Observer: TObserver); virtual; abstract;
procedure Notify; virtual; abstract;
end;
// concrete observable class
TMySubject = class(TObservable)
private
FObservers: TList;
Fx: integer;
public
constructor Create;
destructor Destroy; override;
procedure Add(Observer: TObserver); override;
procedure Remove(Observer: TObserver); override;
procedure Notify; override;
function getX: integer;
procedure setX(value: integer);
end;
// Concrete Observer Object
TMyBar = class(TObserver)
private
FBarX: TProgressBar;
public
constructor CreateBar(aParent: TWinControl);
destructor Destroy; override;
procedure Update(ChangedSubject: TObservable); override;
end;
مرجع:
Ader Gonzalez, Introduction to Design Patterns for Delphi Developers, May 28, 1998