Tasarım Desenleri : Observer Design Pattern

Created with Sketch.

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ış

Yorumunuzu ekleyin