
Tasarım Desenleri : Observer Design Pattern
Bu sefer adını çok duyduğumuz, bir çok yerde karşılaşabildiğimiz, gerçek hayattaki işleyişe çok benzeyen bir tasarım desenini tanıtmaya çalışacağım: Observer Design Pattern.
Observer’ın, kelime anlamı “Gözlemci” demektir. Observer Tasarım Deseni, “özne” konumundaki bir nesnenin kendisine bağımlı olan diğer nesnelere kendisinde gerçekleşen bir koşul, olay veya durum değişikliğini otomatik olarak bildirmesini, bu sayede de onların kendilerini güncellenmesini tetikleyen bir yapıyı tarif eder. Bu yapıda bağımlılıkların listesini tutan ve değişiklikleri takip eden “özne” konumunda bir ana nesne ve bu nesneye bildirim bağlamında bağımlı olan “Gözlemci” konumundaki birden çok alt nesne yer alır. Alt nesnelerin veya abonelerin ortak bir ata nesneden türetilmiş olması tercih edilir. Ortak bir atadan türetilmeseler bile kendi yapılarında bir “Update” metodunu barındırmaları gerekmektedir… Özünde bu tasarım deseni, “Özne” ile “Gözlemciler” arasındaki etkileşimin nasıl yönetileceğini yani bire çok olay işlemeyi nasıl gerçekleştirebileceğimizi tarif etmektedir.
Yukarıdaki paragraftan bu desenin çok basit bir yapısı olduğu izlenimine kapılabilirsiniz. Endişelenmeyin haklısınız, aslında bu kadar basit. Fakat tasarım desenini daha iyi anlamak için bu desenin çalışma biçimini bir hikaye üzerinden de anlatmayı deneyebiliriz…
Diyelim ki siz bir bankacısınız ve müşterilerinize yatırım danışmanlığı yapıyorsunuz. 3 tane müşteriniz var, bu müşteriler Dolar üzerinden al-sat işlemleri yapıyor fakat bu konuda sizden danışmanlık hizmeti alıyor… Siz dolar piyasasını takip ederken dolar düşmeden önce müşterilerinize “sat”, yükselmeden önce “al” önerisinde bulunuyorsunuz. İşte Observer tasarım deseni de aslında bu etkileşimin nasıl yönetileceğini tarif ediyor. bu senaryoya göre siz “Özne” (subject), müşterileriniz “Gözlemci” (observer), sizin “al” veya “sat” talimatlarınız “Bildirim” (Notification, NotifyObservers), müşterilerinizin “alma” veya “satma” eylemi “Güncelleme” (update) anlamına geliyor. Burada birisinin size müşteri olması durumu “aboneliği başlatıyor” (RegisterObserver), müşteriniz ile sizden aranızdaki iş ilişkisinin sona ermesi ise “aboneliği bitiriyor” (UnRegisterObserver). Son olarak portföyünüz ise “Müşteri Listeniz” (yani ObserverCollection) anlamına geliyor…
Bunu bir de aşağıdaki gibi bir UML diyagramında gösterelim.
Yukarıdaki senaryoyu ve UML diyagramını bir Pascal kodu ile örneklendirmeye çalışayım;
unit Observer_Objects_;
interface
uses
Vcl.Dialogs // ShowMessage
, System.Generics.Collections // TList, TObjectList
;
{
Tasarım deseninde önerilen kalıptan uzaklaşmamak adına nesne ve değişken isimlerini UML diyagramındaki isimlerle benzeştirdim.
Bu sayede UML diyagramında gösterilen nesnelerin Delphi kodunda nereye denk geldiğini daha kolay anlamış olacağız. Fakat siz bu
isimlendirmeye uymak durumunda değilsiniz. Bu tasarım deseninde önemli olan, bu tür bir mekanizmayı hayata geçirebilmeniz...
}
type
// Gözlemcilerimiz için ortak bir arabirim tanımlıyoruz. Diğer gözlemci sınıflarını bu arabirimden türeteceğiz
// veya bu arabirimi kullanarak ilgili sınıflara adaptör yazabileceğiz... Bu sayede abonelerin yok edilmesi sırasında
// Bellek yönetimini en az eforla sağlamış olacağız.
IObserver = interface
procedure Update(aTalimat: String); // Bu metod aracılığıyla abonelerin kendierini güncellemesini tetikleyebileceğiz.
end;
// IObserver sınıfından türetilen aşağıdaki iki sınıf ise birbirinden farklı nesnelere dikkatinizi çekmek amacıyla tanımlandı.
// UML Grafiğinde bu durumu sarı renklerde vurgulanmış bir şekilde görmektesiniz.
TConcreateObserver_A = class(TInterfacedObject, IObserver) // bu sınıfı bir bakkal veya bir market gibi düşünün
public
procedure Update(aTalimat: String); // Bu, "A" sınıfına özgü fiili bir güncelleme yapacak..
end;
TConcreateObserver_B = class(TInterfacedObject, IObserver) // bu sınıfı da bir fırıncı veya pastane gibi düşünün
public
procedure Update(aTalimat: String); // Bu ise "B" sınıfına özgü fiili bir güncelleme yapılacak...
end;
// Özne tanımımızı bir Interface olarak yapıyoruz.
ISubject = interface
function GetObserverCollection: TList;
procedure SetObserverCollection(aObserverCollection: TList);
property ObserverCollection: TList // Bu bizim abone listemiz, Sınıfı bir Interface değil de bir Abstract
read GetObserverCollection // sınıf olarak sanımlasaydık bunu bir property
write SetObserverCollection; // yerine doğrudan bir değişken olarak da kullanabilirdik.
procedure NotifyObservers(aTalimat: string); // Bu tüm gözlemcilerimize durum değişikliğini toplu olarak bildirir
procedure RegisterObserver(aObserver: IObserver); // Bu bizim yeni aboneyi listeye eklediğimiz metod
procedure UnRegisterObserver(aObserver: IObserver); // Bu bizim mevcut abonemizi listeden çıkaracak olan metodumuz
procedure TEST(aTalimat: String); // Durum değişikliğinin gerçekleşeceği temsili bir metoddur.
end;
TSubject = class(TInterfacedObject, ISubject)
private
FObserverCollection: TList; // Bu değişken aslında bizim abonelerimizin listesini tutacak
function GetObserverCollection: TList;
procedure SetObserverCollection(aObserverCollection: TList);
procedure NotifyObservers(aTalimat: string);
protected
public
constructor Create;
destructor Destroy; override;
// Bu bizin süreci tetiklediğimiz metod. Bu herhangi başka bir metod da olabilir. UML çiziminde bunu görmezsiniz
// çünkü bu kısımm her türlü tetiklemeyi temsil ediyor.
procedure TEST(aTalimat: String);
procedure RegisterObserver(aObserver: IObserver);
procedure UnRegisterObserver(aObserver: IObserver);
property ObserverCollection: TList
read GetObserverCollection
write SetObserverCollection;
end;
implementation
{ TSubject }
constructor TSubject.Create;
begin
inherited Create;
FObserverCollection := TList.Create;
end;
destructor TSubject.Destroy;
begin
FObserverCollection.Destroy; // FreeAndNil ( FObserverCollection ); de kullanılabilir.
inherited Destroy;
end;
procedure TSubject.RegisterObserver(aObserver: IObserver);
begin
GetObserverCollection.Add(aObserver)
end;
procedure TSubject.UnRegisterObserver(aObserver: IObserver);
begin
GetObserverCollection.Remove(aObserver);
end;
procedure TSubject.NotifyObservers(aTalimat: string);
var
aObserver: IObserver;
begin
for aObserver in GetObserverCollection // Gözlemci listemizdeki her bir gözlemciyi tek tek ziyaret edip
do aObserver.Update(aTalimat); // kendisini güncellemesini sağlıyoruz.
end;
function TSubject.GetObserverCollection: TList;
begin
Result := FObserverCollection;
end;
procedure TSubject.SetObserverCollection(aObserverCollection: TList);
begin
FObserverCollection := aObserverCollection;
end;
procedure TSubject.TEST(aTalimat: String);
begin
// Burası ise fiilen durumun değiştiği kök noktamız.
ShowMessage('Gözlemci Listesindeki her bir gözlemciye şu talimatı verdim = ' + aTalimat);
// Durum değişikliği gerçekleştikten sonra oluşan yeni konjonktürü gözlemcilerimize bildiriyoruz.
NotifyObservers(aTalimat);
end;
{ TConcreateObserver_A }
procedure TConcreateObserver_A.Update(aTalimat: String);
begin
// A tipindeki bu gözlemciyi mesela bir bakkal olarak düşünebilirsiniz
ShowMessage('A Sınıfına göre değişik bir güncelleme işlemi yaptık = ' + aTalimat);
end;
{ TConcreateObserver_B }
procedure TConcreateObserver_B.Update(aTalimat: String);
begin
// B tipindeki bu gözlemcisi ise misalen bir fırıncı olarak düşünebilirisinis.
ShowMessage('B Sınıfına göre çok farklı, emsalsiz bir başka güncelleme işlemi daha yaptık = ' + aTalimat);
end;
end.
Gelelim, şimdi bu yapının nasıl kullanıldığına.
implementation
uses
Observer_Objects_ // Sınıfın tanımlı olduğu Uniti kullanacağız.
;
{$R *.dfm}
procedure TAna.Button1Click(Sender: TObject); // Bir butonun OnClick olayına
var
KurTakipcisi: TSubject; // özne konumundaki bir nesneyi değişken olarak tanımladık
begin
KurTakipcisi := TSubject.Create; // Özneyi oluşturduk
KurTakipcisi.RegisterObserver(TConcreateObserver_A.Create); // A türünden bir gözlemci / abone / alt nesne oluşturduk
KurTakipcisi.RegisterObserver(TConcreateObserver_B.Create); // B türünden başka bir gözlemci / abone / alt nesne oluşturduk
KurTakipcisi.RegisterObserver(TConcreateObserver_A.Create); // A türünden başka bir gözlemci / abone / alt nesne oluşturduk
KurTakipcisi.RegisterObserver(TConcreateObserver_B.Create); // B türünden başka bir gözlemci / abone / alt nesne oluşturduk
KurTakipcisi.TEST('Al'); // durum değişikliğini tetiklemek için "AL" emri verdik
FreeAndNil(KurTakipcisi); // Öznemizi ve ona bağlı olan tüm aboneleri bellekten temizledik.
end;
// VEYA BAŞKA BİR KULLANIM ŞEKLİ (ASLINDA AYNI. SADECE ABONELERİN KİM OLDUĞUNU DAHA NET BİLİYORUZ )
procedure TAna.Button2Click(Sender: TObject); // BAŞKA bir butonun OnClick olayına
var
KurTakipcisi : TSubject; // Özne konumunda olan bir BANKACIYI değişken olarak tanımladık
Firinci : TConcreateObserver_A; // A tipli
Bakkal : TConcreateObserver_B; // B tipli
begin
Firinci := TConcreateObserver_A.Create; // Bir fırıncı oluşturduk.
KurTakipcisi := TSubject.Create; // Bankacımızı oluşturduk.
KurTakipcisi.RegisterObserver( Firinci ); // Fırıncımızı abone listesine aldık
KurTakipcisi.RegisterObserver( TConcreateObserver_B.Create ); // B tipinde başka birisi bize abone oldu
Bakkal := TConcreateObserver_B.Create; // Bir bakkal oluşturduk
KurTakipcisi.RegisterObserver( Bakkal ); // Bakkalımızı abone listesine aldık
KurTakipcisi.TEST('Sat'); // tüm abonelere Satma yönünde talimat verdik
KurTakipcisi.UnRegisterObserver( Firinci ); // Fırıncı bizim hizmetten memnun kalmadığı için listemizden çıkardık...
KurTakipcisi.TEST('Al'); // geri kalan tüm abonelere Alma yönünde talimat verdik...
FreeAndNil(KurTakipcisi);
end;
Yorum yapılmamış