Konuyu Oyla:
  • Derecelendirme: 5/5 - 1 oy
  • 1
  • 2
  • 3
  • 4
  • 5
Tasarım Desenleri : Factory Method
#1
Tamamını elden geçirmektense sadece tek bir parametreyi değiştirerek projenizin mevcut davranışını yeniden düzenlemek veya ek davranışlar geliştirmek istediğiniz zamanlar oldu mu? "Biz zaten böyle şeyler yapabiliyoruz" dediğinizi duyar gibiyim... Kazın ayağı gerçekten de öyle mi acaba? Bu konuda birileri kafa yormuş...

Problem

Büyük veya gereğinden fazla şişmanlayan projelerin temel bir sorunu vardır: İyi planlanmamışsa zamanla dinamizmini kaybeder ve yavaş yavaş daha statik, daha hantal bir şekle bürünürler. Bazıları için bu pekde önemli bir konu olmayabilir fakat projeniz canlıysa ve yeni talepler gelmeye devam ediyorsa bu durum sizin için eziyet haline de dönüşebilir.

Diyelim ki, projenizde log tutmak için veritabanından faydalanıyorsunuz ve işin en başından beri loglama sürecini böyle planlamışsınız ve uzun zamandırda bu iş böyle yürümüş gitmiş... E güzel, log tutulabilecek her noktada bununla ilgili bigüzel kodlar da yazmışsınız... Sıkıntı yokmuş gibi gözüküyor değilmi, çünkü siz yapılması gereken herşeyi yapmış ve işleri rayına oturtmuşsunuz... Peki, tamam, günün birinde müşterinin teki log bilgilerinin kendisine Eposta ile bildirilmesini istedi diyelim. Veya bir başkası çıkıp "bana bu log bilgileri SMS ile gelsin", ya da başka bir müşteri "veritabanımı loglarla doldurmak istemiyorum, onun yerine client'lerde text dosyada tutulsun" gibisinden taleplerde bulundu diyelim... Bu durumda Projenizde değiştirmek zorunda olduğunuz bir davranış için bir çok noktayı elden geçirmek zorunda kalabileceğinizin farkında mısınız? Veya şöyle söyleyeyim; "Ya ne güzel, mutlu mesut loglarımı veritabanında tutuyordum, nereden çıktı bu abuk subuk istekler" diye düşünmeye başladınız belki de... İşte problemin kendisi aslında bu, yani koskoca projeyi tamamen elden geçirmek ve her noktasına müdahale etmek... Normalde çalışan ana kodunuza müdahale ediyorsanız bilin ki kökten yanlış bir şey yapıyorsunuz, çünkü bunun bir bedeli var. Halbuki bunun basit bir çözümü olabilir...

Çözüm

Herşeyden önce, bu tarz müşteri taleplerinin herzaman gelebileceğini önkoşul olarak zaten en baştan kabul etmiş olmanız gerekiyor... O nedenle işin proje yönetimi kısmına zaten değinmeyeceğim (ki konumuz da değil zaten) fakat teknik boyuta gelecek olursak; bu noktada literatür bize "Factory Method" (Fabrika Yöntemi) tekniklerini kullanmamız gerektiğini öneriyor...

Bu tasarım deseninin şöyle bir tanımı var; "Factory Method modeli, Sınıf Tabanlı programlamada oluşturulacak nesnenin, sınıfın kendisini doğrudan belirtmek zorunda kalmadan, nesne oluşturma sorunuyla başa çıkmak için, Factory Method zihniyetini kullanan bir üretim desenidir. Bu, bir arabirim tarafından çağrılan, bir arabirim tarafından belirtilen ve alt sınıflar tarafından uyarlanan (implemente edilen) veya bir temel sınıfta uygulanan ve istenirse türetilmiş sınıflara göre bir fabrika yöntemini çağırarak, bir kurucu çağırmak yerine, nesneler oluşturarak yapılır." diyor...

Yani biz bundan ne anlıyoruz? Özetleyecek olursak; işi yapacak olan fiili, gerçek nesneler ile işi talep edecek olan nesneler arasındaki bağlantıyı soyut bir sınıf veya (soyut) bir arabirim vassıtasıyla yapın. Böylece tür tipleri ve dönüşümleri ile uğraşmaktan, bir çok yerde IF blokları ile cebelleşmekten kurtulun...

Yani aslında buradan da anlaşıldığı üzere Factory Method'un temel amacı, değişkenlik gösterebilecek, zamanla farklı tekniklerin uyarlanabilmesini sağlayabilecek bir yapıyı barındırması nedeniyle yazılımlarımızda, kodlamalarımızda değişimi kontrol altına alabilmemizi sağlamaktır.

Nasıl?

Peki biz bunu nasıl yapacağız? Kavramın kendisi soyut olduğu için somut bir örnek üzerinden bu konuyu anlatmak herhalde daha uygun ve kolay olacaktır. Yazının en başındaki örnek üzerinden ilerleyelim isterseniz. Konunun daha iyi anlaşılabilmesi adına tasarım desenindeki bazı nesleleri belli unit'lerde toparladım, bu sayede tasarım desenini, iş nesnelerinizi ve ana uygulamanızı birbirinden daha kolay ayırdedebilmenizi hedefledim.

Önce arabirimlerimizi tanımlayalım;

unit FactoryPattern_Objects;

interface

uses
 Vcl.Dialogs;

type
 { Arabirimlerimizi tanımlıyoruz }
 ILogcu = interface                                           // Bu bizim log tutmak için kullanacağımız TEMSİLİ bir arabirim ( Soyuttur kendileri... )
    procedure Log(const aLog: String);                         // Bu da bizim log tutmak için kullanacağımız yine TEMSİLİ bir METODUMUZ ( Bu da soyut... )
 end;
 ILogFabrikasi = interface                                    // Bu bizim log fabrikası üretmek için kullandığımız TEMSİLİ bir arabirim ( Bu da soyut )
   function CreateLogcu: ILogcu;                              // Bu da bizim FACTORY METHOD ile kastettiğimiz fabrikamız... Olay aslında bunu tanımlamakla başlıyor...
 end;

 { LOGCU Sınıflarımızı tanımlıyoruz }
 TLogcu_SQL = class(TInterfacedObject, ILogcu)                // Bu SQL ile log tutan "GERÇEK" bir sınıf örneğimiz, arabirimimizi implemente edeceğimiz gerçek nesnelerden birisi bu olacak
   public
     procedure Log(const aLog: String);                       // Logu SQL'de tutacak olan "GERÇEK" bir metod. Bunun içinde fiilen SQL Scriptlerini çalıştıran bir kod olması gerekecek...
 end;
 TLogcu_SMS = class(TInterfacedObject, ILogcu)                // Bu SMS ile log tutan "GERÇEK" bir sınıf örneği, bunu da ILOGCU'dan türettik!
   public
     procedure Log(const aLog: String);                       // Logu SMS ile gönderecek olan "GERÇEK" bir metod. Bunun içinde fiilen SMS gönderen bir kod olması gerekecek...
 end;
 TLogcu_MAIL = class(TInterfacedObject, ILogcu)               // Bu da EPOSTA ile log tutan yine "GERÇEK" bir sınıf örneği, bunu da ILOGCU'dan türettik!
   public
     procedure Log(const aLog: String);                       // Logu EPOSTA ile gönderecek olan "GERÇEK" bir metod. Bunun içinde fiilen EPOSTA gönderen bir kod olması gerekecek...
 end;

 { FACTORY METHOD'larımızı tutan sınıfları tanımlıyoruz }
 TLogFabrikasi_SQL = class(TInterfacedObject, ILogFabrikasi)  // SQL için kullanacağımız loglama sınıfını üretecek olan fabrika sınıfımız
   public
     function CreateLogcu: ILogcu;                            // FABRİKA METODUMUZ, Dananın kuyruğunun koptuğu yerlerden birisi burası
 end;
 TLogFabrikasi_SMS = class(TInterfacedObject, ILogFabrikasi)  // SMS için kullanacağımız loglama sınıfını üretecek olan fabrika sınıfımız
   public
     function CreateLogcu: ILogcu;                            // FABRİKA METODUMUZ, Dananın kuyruğunun koptuğu yerlerden birisi de burası
 end;
 TLogFabrikasi_MAIL = class(TInterfacedObject, ILogFabrikasi) // EPOSTA için kullanacağımız loglama sınıfını üretecek olan fabrika sınıfımız
   public
     function CreateLogcu: ILogcu;                            // FABRİKA METODUMUZ, Dananın kuyruğunun koptuğu yerlerden birisi de burası
 end;

implementation

{ TLogcu_SMS }

procedure TLogcu_SMS.Log(const aLog: String);
begin
 ShowMessage('SMS ile loglama yapıldı: ' + aLog);
 // Burada SMS gönderen bir kod olduğunu varsayalım ve öyle düşünelim...
 // çünkü mevzu logun SMS ile nasıl gönderildiği değil...
 // ...
 // ...
end;

{ TLogcu_SQL }

procedure TLogcu_SQL.Log(const aLog: String);
begin
 ShowMessage('SQL ile loglama yapıldı: ' + aLog);
 // Burada SQL veritabanına LOG atan bir kod olduğunu varsayalım...
 // ...
 // ...
 // ...
end;

{ TLogcu_MAIL }

procedure TLogcu_MAIL.Log(const aLog: String);
begin
 ShowMessage('Mail ile loglama yapıldı: ' + aLog);
 // Burada EPOSTA ile LOG gönderen bir kod olduğunu varsayalım...
 // ...
 // ...
 // ...
end;

{ TLogFabrikasi_SQL }

function TLogFabrikasi_SQL.CreateLogcu: ILogcu;
begin
 // Factory Method tasarım deseninin tanımına göre log tutan bir nesne üretip dışarı gönderdik...
 Result := TLogcu_SQL.Create;
end;

{ TLogFabrikasi_SMS }

function TLogFabrikasi_SMS.CreateLogcu: ILogcu;
begin
 // Factory Method tasarım deseninin tanımına göre log tutan bir nesne üretip dışarı gönderdik...
 Result := TLogcu_SMS.Create;
end;

{ TLogFabrikasi_MAIL }

function TLogFabrikasi_MAIL.CreateLogcu: ILogcu;
begin
 // Factory Method tasarım deseninin tanımına göre log tutan bir nesne üretip dışarı gönderdik...
 Result := TLogcu_MAIL.Create;
end;

end.

Yukarıdaki örnekte de anlaşıldığı üzere bir Factory Method tanımının nasıl yapıldığına dair doyurucu bir örnek gördüğümüze göre artık bu yapıyı projelerimizde nasıl kullanacağımıza bakmalıyız. Bunun için bize bir kobay lazım. Burada Kobay'dan kasıt, sizin iş nesneleriniz olacak, kapsamı geniş olduğundan sadece kobay diye isimlendiriyorum fakat sizin için bu bir ORM nesnesi, bir dataset veya bir form bile olabilir... Alelade bir kobay nesnesi üreterek bu mekanizmanın nasıl çalıştığını daha iyi anlayabiliriz.

Ama ondan önce, yani daha iyisini önermeden önce ben de dahil zamanında birçoğumuzun sıklıkla düştüğü bir yanılgıya, kötü bir kodlamaya örnek vermek gerekiyor. Bu örnek önemli, çünkü konunun kıymetini arttırıyor. KirliKobay adıyla isimlendirdiğim nesneye daha yakından bakalım;

interface

type
 TKirliKobay = class                    //  Sıradan bir sınıf tanımı
   private
     Logger: TLogcu_SQL;                //  Böyle bir tip belirtimi sizi bu nesneye (TLogcu_SQL'e) bağımlı yapar. İşte bunu yapmayın!
   public
     constructor Create;                //  Klasik, herhangi bir nesneyi üretirken kulllandığımız sıradan bir CONSTRUCTOR metodu
     destructor Destroy; override;      //  Klasik yok edici metodumuz...
     procedure Kaydet;                  //  Loglamayı tetikleyeceğimiz nokta...
 end;

implementation

uses
 System.SysUtils;   // FreeAndNil

constructor TKirliKobay.Create;
begin
 inherited Create;                      //  
  Logger := TLogcu_SQL.Create;           //  Doğrudan TLoguc_SQL sınıfını ürettik ama bu modelde bu aslında yanlış bir davranış...
end;

destructor TKirliKobay.Destroy;
begin
 freeandnil(Logger);                    //  Doğal olarak, arkamızı temizlemek durumunda da kalıyoruz...
  inherited Destroy;                     //  
end;

procedure TKirliKobay.Kaydet;
begin
 ShowMessage('TKirliKobay bir şeyleri kaydetti');
  Logger.Log('Kirli bir loglama yaptık dolayısıyla TLogcu_SQL''e artık mahkumuz');
end;

TKirliKobay nesnesindeki Logger değişkenini doğrudan TLogcu_SQL nesnesine bağladık. İşte problem olan, yanlış olan nokta aslında burası. Çünkü bu nokta sizin hem kodunuzun esnekliğini bozuyor hem de o nesneye bir bağımlılık yaratıyor. Halbuki siz istediğiniz log nesnesiyle çalışabilmeli bununla birlikte ana kodunuz da bu değişiklikten "hiç bir şekilde" etkilenmemeli... Yukarıdaki KirliKobay nesnesi yine çalışır, yine bir şeyleri kaydeder ve SQL'e loglar atmaya yine devam eder, yani yine iş görür ama SQL dışında başka bir loglama tekniği kullanmak isterseniz o zaman sizin başınızı ağrıtır.

Eğer daha esnek bir yapı, nesnelerden daha bağımsız bir düzen kurmak istiyor ve ileriye dönük bir yatırım yapmak niyetindeysek bunun doğrusu aşağıdaki gibi bir yapı ile sağlanmalı;

unit Kobay_Objects_;

interface

uses
   Vcl.Dialogs                 //  ShowMessage
 , FactoryPattern_Objects      //  ILogcu, ILogFabrikasi
 ;

type
 ELogTipi = (ltSQL, ltSMS, ltMAIL);                                  //  Bu Enumerated Type, bizim bir nesnemizde hangi log ile çalışacağımızı seçmemize yardım edecek, elzem değil, tamamen bir tercih meselesidir...
 TKobay = class                                                      //  Bu bizim kurgusal bir nesnemiz, bunu siz bir TForm, bir TUniConnection gibi bir şey olarak da düşünebilirsiniz
   private
     Logger        : ILogcu;                                         //  Log tutacağımız nesnenin veri tipinin bir INTERFACE'yi kullandığına dikkat edin !!!
     LoggerFactory : ILogFabrikasi;                                  //  Log tutma nesnesini üreteceğimiz nesnenin veri tipinin bir INTERFACE'ye işaret ettiğine dikkat edin !!!
     FLogTipi      : ELogTipi;
     procedure SetLogTipi(const Value: ELogTipi);
   public
     constructor Create;                                             //  Klasik, herhangi bir nesneyi üretirken kulllandığımız sıradan bir CONSTRUCTOR metodu
     procedure Kaydet;                                               //  LOGLAMAYI TETİKLEYECEĞİMİZ METOD.
     property LogTipi: ELogTipi read FLogTipi write SetLogTipi;      //  Logcumuzu bununla belirleyeceğiz...
 end;

implementation

{ TBirNesne }

constructor TKobay.Create;       // Kurgusal nesnemizi üreteceğimiz CONSTRUCTOR metod.
begin
 inherited Create;              // Kurgusal nesnemizi üretiyoruz.
 LogTipi := ltSQL;              // Bir başlangıç değeri olarak SQL ile loglama yapacağımızı belirtmiş olduk.
end;

procedure TKobay.SetLogTipi(const Value: ELogTipi);
begin
  FLogTipi := Value;

 // Bu noktada biz yine soyut bir arabirim vasıtasıyla Logcu üreten bir fabrikadan faydalanmak istediğimizi belirtiyoruz
  // Seçtiğimiz tipe göre fabrika arabirimimiz bize bir LOG FABRİKASI üretiyor
  case  Value   of
        ltSMS : LoggerFactory := TLogFabrikasi_SMS.Create;
        ltSQL : LoggerFactory := TLogFabrikasi_SQL.Create;
        ltMAIL: LoggerFactory := TLogFabrikasi_MAIL.Create;
  end;

 // Bu noktada hangi fabrikayı kullandığımızın artık bizim için bir anlamı yok, çünkü hangi fabrika olursa olsun, biz bize bir log tutan nesne geleceğini artık biliyoruz.
 Logger := LoggerFactory.CreateLogcu; // Yukarıdaki tercihimiz ne olursa olsun artık, fabrikamız bize bir LOGCU nesnesi üretiyor
end;

procedure TKobay.Kaydet;
begin
 // Bu metod, bizim kobay nesnemizin birşeyleri kaydettiği nokta.
 // Temsili olarak verinin bir yerlere kaydedildiğini varsayalım...
 ShowMessage('Veri Kaydedildi');

 // Burası ise LOG tuttuğumuz nokta, şimdi size bir soru LOGGER hangi nesneyi kullanıyor? LOGGER'in tipi ne sizce?
  Logger.Log('TBirNesne.Kaydet');   // Logger'in tipinin ne olduğunun artık bu noktadan sonra bir önemi yok çünkü siz bunu zaten yukarıda belirttiniz, sizin için artık bu noktadan sonra log düzgün tutulmuş mu tutulmamış mı ona bakmanız gerekir...
end;

end.

Kobayımızı da tanımladığımıza göre artık projemizde bunu nasıl kullanabilirve test edebiliriz basit bir örnekle görelim;

unit Ana_;

interface

uses
 Kobay_Objects_, // TKobay  ( BU ARADA LOGCU VE LOGFABRIKASI ile ilgili herhangi bir uniti çağırmadık dikkat ettiyseniz... )

 Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
 TAna = class(TForm)
   BT_Save: TButton;
   procedure FormCreate(Sender: TObject);
   procedure FormDestroy(Sender: TObject);
   procedure BT_SaveClick(Sender: TObject);
 private
   { Private declarations }
 public
   { Public declarations }
   NesneninTeki: TKobay;
 end;

var
 Ana: TAna;

implementation

{$R *.dfm}

procedure TAna.FormCreate(Sender: TObject);
begin
 // Formumuzun OnCreate olayına kobay nesnemizi üreten bir kod ekliyoruz
 NesneninTeki := TKobay.Create;
end;

procedure TAna.FormDestroy(Sender: TObject);
begin
 // Formumuz yok edilmeden hemen önce kobay nesnemizi yok ediyoruz...
 FreeAndNil(NesneninTeki);
end;

procedure TAna.BT_SaveClick(Sender: TObject);
begin
 //  SAVE adlı butona bastığımızda kobay nesnenin loglama tipini her seferinde değiştiren bir yapı kurduk
 //  Bu sadece yapının dinamikliğini sergilemek amacıyla böyle yapıldı, butona her bastığınızda loglama metodu değişecek...
 case NesneninTeki.LogTipi of
      ltSMS : NesneninTeki.LogTipi := ltSQL;
      ltSQL : NesneninTeki.LogTipi := ltMAIL;
      ltMAIL: NesneninTeki.LogTipi := ltSMS;
 end;

 // kobay nesnemiz bir şeyleri kaydedip hemen kendi içinde bir de log tutuyor fakat bu işler arka tarafta yapılmış oluyor...
  NesneninTeki.Kaydet;
end;

end.

Gördüğünüz gibi başlarda çok karmaşıkmış gibi gelse de aslında gayet basit ve gayette temiz bir yapı. Projenizdeki kodu yönetebilmenize ve onu daha esnek hale getirebilmenize çok fayda sağlıyor. İşin başında belki biraz yorulabilirsiniz ama terlediğinize değecektir...

Bu arada fark ettiniz mi bilmiyorum ama kobay nesnemiz haricinde hiç bir yerde free ve destroy gibi dispose edici, yokedici metodları çağırmadık. Sizce bir yerlerde memory leak oluşmuş mudur? ( Bunu bir ev ödevi olarak kabul edin ve sebeplerini birazcık araştırın, interface'ler altında yatan harika bir mekanizmanın olduğunu fark edeceksiniz Smile )

Umarım faydalı olmuştur, başka bir yazıda görüşmek üzere...
YouTube Delphi Tips
"Yaşlanarak değil, yaşayarak tecrübe kazanılır. Zaman insanları değil, armutları olgunlaştırır" Peyami Safa
WWW
Cevapla
#2
Teşekkürler Uğur bey
Sayenize bilmediğim bir konudan haberdar olmuş oldum.
Cevapla
#3
Emeğinize sağlık...
There's no place like 127.0.0.1
WWW
Cevapla
#4
Ellerinize sağlık.
Mal sahibi, mülk sahibi
Hani bunun ilk sahibi ?
Mal da yalan mülk de yalan
Var biraz da sen oyalan...
WWW
Cevapla
#5
Cok tesekkurler
Cevapla
#6
Ellerinize sağlık
"…De ki: "Hiç bilenlerle bilmeyenler bir olur mu? Şüphesiz, temiz akıl sahipleri öğüt alıp-düşünürler" (Zümer Suresi, 9)
Cevapla
#7
elinize sağlık , güzel bir makale olmuş.

interface kullanmadan abstract sınıflarla bu deseni uygulamak isteyenler olursa asagidaki bağlantıdan benzer bir örneğe ulaşabilirler.

http://web.archive.org/web/2007081917115...m:80/?p=60

burda da bir kaç farklı desen daha mevcuttur.

http://sadettinpolat.blogspot.com/2017/0...akaym.html
WWW
Cevapla
#8
(10-08-2018, Saat: 22:27)frmman Adlı Kullanıcıdan Alıntı: Teşekkürler Uğur bey Sayenize bilmediğim bir konudan haberdar olmuş oldum.
Rica ederim, birlikte öğreniyoruz.

(10-08-2018, Saat: 23:22)SimaWB Adlı Kullanıcıdan Alıntı: Emeğinize sağlık...
Teşekkür ederim

(10-08-2018, Saat: 23:41)Tuğrul HELVACI Adlı Kullanıcıdan Alıntı: Ellerinize sağlık.
Teşekkür ederim

(11-08-2018, Saat: 10:46)klavye Adlı Kullanıcıdan Alıntı: Cok tesekkurler
Bilmukabele,

(11-08-2018, Saat: 11:16)cinarbil Adlı Kullanıcıdan Alıntı: Ellerinize sağlık
Teşekkür ederim

(12-08-2018, Saat: 03:21)sadettinpolat Adlı Kullanıcıdan Alıntı: elinize sağlık , güzel bir makale olmuş.

interface kullanmadan abstract sınıflarla bu deseni uygulamak isteyenler olursa asagidaki bağlantıdan benzer bir örneğe ulaşabilirler.

http://web.archive.org/web/2007081917115...m:80/?p=60

burda da bir kaç farklı desen daha mevcuttur.

http://sadettinpolat.blogspot.com/2017/0...akaym.html
Teşekkür ederim, Bu makalenin devamı niteliğinde Abstract Factory ile ilgili başka bir yazı yazmayı düşünüyordum denk düştü.
YouTube Delphi Tips
"Yaşlanarak değil, yaşayarak tecrübe kazanılır. Zaman insanları değil, armutları olgunlaştırır" Peyami Safa
WWW
Cevapla
#9
Emeğinize sağlık, çok faydalı bir makale olmuş.
Faydalanılmayan bilgi, harcanmayan ve hiç kimseye hayrı dokunmayan define gibidir.

Hz. Muhammed (sav.)
Cevapla
#10
https://youtu.be/BNTaJu2CVBg?t=338

Bu moda girdim abi, teşekkür ederim. Aydınlattığın için <3
kisisel_logo_dark.png
WWW
Cevapla


Konu ile Alakalı Benzer Konular
Konular Yazar Yorumlar Okunma Son Yorum
  Tasarım Desenleri : Singleton Design uparlayan 16 13.090 04-12-2020, Saat: 10:38
Son Yorum: narkotik
  Tasarım Desenleri : Fluent Design uparlayan 19 11.842 17-08-2020, Saat: 18:15
Son Yorum: uparlayan
  Tasarım Desenleri : Observer Design Pattern 2 uparlayan 4 2.429 08-08-2020, Saat: 10:43
Son Yorum: Bay_Y
  Fonksiyon ve Method Tipleri ismailkocacan 4 4.135 05-08-2020, Saat: 18:02
Son Yorum: Bay_Y
  Tasarım Desenleri : Model View Controller uparlayan 7 4.057 22-07-2020, Saat: 10:25
Son Yorum: uparlayan



Konuyu Okuyanlar: 1 Ziyaretçi