Tasarım Desenleri : Singleton Design

Created with Sketch.

Tasarım Desenleri : Singleton Design

Hiç projenizin tamamında “şu nesneden sadece 1 tane üretilebilsin, başka da üretilemesin” dediğiniz anlar oldu mu?

Problem

Proje büyüdükçe yönetilmesi gereken nesnelerin sayısı da çoğalıyor. Haliyle bu durum bazen karmaşaya da sebep olabiliyor. Fakat bazende öyle durumlar oluyor ki nesne sayısı çok olmasa bile bazı nesnelerin sadece bir kere üretilmesi işi çözüyor. 

Projede tek başınızayken bu tarz nesneleri idare etmek nispeten kolaydır, zira kaynak koda hakimsinizdir. Sonuçta birden fazla kez oluşturmazsınız olur biter… Peki, durum öyle değilse, yani unutursanız, veya projeye başkaları dahil olduysa o zaman ne yapılacak? İzlenecek yol belli değilse zamanla kaynak koddaki zarafetin yavaş yavaş yerini kaosa bıraktığı görülür…

Çözüm

Bu tarz, sadece bir kere oluşturulması gereken bazı nesneleriniz için Singleton Desenini kullanmak yeterli… Örnek vermek gerekirse, ben bazı projelerimde o projenin kişiliğini oluşturan belli başlı değişken gruplarını tek bir nesnede toplar ve o nesne üzerinden içeride projeyi yönetirim. Misal, kullanıcının oturum bilgileri veya o an hangi firma bilgileriyle çalışıldığını tutmak gibi örnekler verilebilir. Siz de bu tarz bir şeye ihtiyaç duyuyorsanız bu tasarım örneğini kullanmanızı tavsiye ederim.

Nasıl?

Singleton tipi bir nesne tanımı yapmanın sıradan bir sınıf tanımı yapmaktan pek bir farkı yoktur. Fark, özünde Create yerine GetInstance kullanılmasını sağlamaktadır. Nesneyi doğrudan Create etmeye kapatmak, ve bunun doğuracağı boşluğu da bu işlevi (yani Create’i) ikame edecek şekilde bir GetInstance fonksiyonu yazmaktır. 

Örnek

type
  TSingletonOrnegi = class                        // Bilinen şekilde sade, basit bir sınıf tanımı, yani hepimizin bildiği TObject'ten türetilmiş bir sınıf...
  strict private                                  // Nesne dışından erişilemeyecek olan unsurlarımızı bu bölgede tanımlıyoruz
    constructor Create;                           // Böylece Kod yazarlarının bunları doğrudan kullanmalarını engellemiş oluyoruz.
    class var FInstance: TSingletonOrnegi;        // Nesne dışından erişilemeyen bu sınıf değişkeni, bizim, nesnenin "Tekilliğini" sağlamamıza yarayacak.
  public                                          // Herkese açık, ortaklaşa kullanılabilecek unsurlarımızı da bu bölgede tanımlıyoruz...
    class function GetInstance: TSingletonOrnegi; // Nesne "yoksa" üretir, "varsa" mevcut nesneye erişir... Bu fonksiyonun veri tipi sınıfın kendisidir...
  end;

implementation

{$R *.dfm}

{ TSingleton }

constructor TSingletonOrnegi.Create;
begin
  // Bu oluşturucu, "Strict Private" bölümünde tanımalndığı için dışarıdan doğrudan kullanılamaz.
  // Dolayısıyla doğrudan çağırılsa bile herhangi bir tepki vermeyecektir.
  inherited Create;

  { BU NOKTADA KOD YAZARLARINA GETINSTANCE METODUNU KULLANMALARI YÖNÜNDE BİR HATIRLATMA YAPMAK GEREKEBİLİR...}

  // Bunu test etmeniz amacıyla ekledim, silebilirsiniz.
  ShowMessage('OnCreate');
end;

class function TSingletonOrnegi.GetInstance: TSingletonOrnegi;
begin
  // Makalemizin can alıcı yordamı aslında burası, devam eden satırlarda bu nesnenin bir örneğinin oluş olmadığı sorgulanıyor
  // Yoksa hemen bir adet üretiliyor.
  // Varsa herhangi bir üretim söz konusu değil.
  if (FInstance = nil) then FInstance := TSingletonOrnegi.Create();

  // Sonrasında da sonuç değeri olarak örneğin kendisini dışarı veriyor...
  Result := FInstance;

  // Bunu da test etmeniz amacıyla ekledim, silebilirsiniz.
  ShowMessage('OnInstance');
end;

Ne Yaptık?

Görüldüğü gibi TSingletonOrnegi adlı nesnemizin constructoru olan Create yordamını Strict Private bölümüne taşıyarak dışarıdan erişime kapattık. Artık Create yordamı sadece nesnenin içerisindeki diğer metodlar tarafından ulaşılabilir hale geldi. Böylece Singleton sistematiğinin ilk adımını tamamlamış olduk. 

Singleton deseninin ikinci adımında ise Class Variable yapısını kullandık. Bildiğiniz üzere bir sınıf içerisindeki herhangi bir fonksiyon, prosedür, özellik, nitelik veya değişken tanımının başında class ibaresi getirilmişse bilinki nesnenin o kısmını nesneyi create etmeden, doğrudan kullanabilirsiniz. Bunu bir nevi sınıfın global bir değişkeni gibi düşünebilirsiniz… (Bunun mekaniğine bu makaleye yorum yazacak olan arkadaşlar mutlaka değinecektir diye düşünüyorum…)

Kaldığımız yerden devam edecek olursak; devamında da FInstance adlı TSingletonOrnegi tipinde bir sınıf değişkenini yine Strict Private bölümünde  tanımladık. Bu bölgede tanımlamamızın iki sebebi var. Birincisi nesne henüz ortada yokken bile biz bu nesnenin bir kopyası üretilmiş mi kontrol edebilelim. İkincisi ise bu değişkene dışarıdan müdahale olmasın, sadece sınıfın kendi içinden erişilebilsin…

Bu sayede biz, sınıfımızın çalışma esnasında bir örneğinin oluşturulup oluşturulmadığını kontrol edebilir hale gelmiş olduk. 

Son olarak, public bölümünde yine veri tipi TSingletonOrnegi olan bir Class Function tanımladık. Yukarıda belirttiğim mekanizma üzerinden nesnemizin daha önceden üretilip üretilmediğini sorgulayan, üretilmemişse de Strict Private bölümüne taşıdığımız Create oluşturucusunu çalıştıran bir fonksiyonla tasarımımızı tamamlamış olduk.

Kullanımı

Kısa cevap aşağıdaki gibi

Tek := TSingletonOrnegi.GetInstance;

Uzun cevap ise şöyle;

Şimdi bu örneğin nasıl kullanıldığını inceleyelim. Bunun için ben boş bir VCL Projesi oluşturup formun üzerine 2 adet TButton ve bir adet TMemo nesnesi ekledim. İlk butona bu örneğin normalde nasıl kullanılması gerektiği ile ilgili bir kod yazdım. İkincisine ise normalde bir sınıfı oluştururken hangi yolu izliyorsak onu yazdım. Anlatım bütünlüğünü bozmamak adına kaynak kodun tamamını aşağıya ekledim ve kaynak koda bazı açıklamalar ekledim.

unit Unit1;

interface

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

type
  TSingletonOrnegi = class                        // Bilinen şekilde sade, basit bir sınıf tanımı, yani bildiğiniz TObject'ten türetilmiş bir sınıf...
  strict private                                  // Nesne dışından erişilemeyecek olan unsurlarımızı bu bölgede tanımlıyoruz
    constructor Create;                           // Böylece Kod yazarlarının bunları doğrudan kullanmalarını engellemiş oluyoruz.
    class var FInstance: TSingletonOrnegi;        // Nesne dışından erişilemeyen bu sınıf değişkeni, bizim, nesnenin "Tekilliğini" sağlamamıza yarayacak.
  public                                          // Herkese açık, ortaklaşa kullanılabilecek unsurlarımızı da bu bölgede tanımlıyoruz...
    class function GetInstance: TSingletonOrnegi; // Nesne "yoksa" üretir, "varsa" mevcut nesneye erişir...
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  Tek  : TSingletonOrnegi;

implementation

{$R *.dfm}

{ TSingleton }

constructor TSingletonOrnegi.Create;
begin
  // Bu oluşturucu, "Strict Private" bölümünde tanımalndığı için dışarıdan doğrudan kullanılamaz.
  // Dolayısıyla doğrudan çağırılsa bile herhangi bir tepki vermeyecektir.
  inherited Create;

  { BU NOKTADA KOD YAZARLARINA GETINSTANCE METODUNU KULLANMALARI YÖNÜNDE BİR HATIRLATMA YAPMAK GEREKEBİLİR...}

  // Bunu test etmeniz amacıyla ekledim, silebilirsiniz.
  ShowMessage('OnCreate');
end;

class function TSingletonOrnegi.GetInstance: TSingletonOrnegi;
begin
  // Makalemizin can alıcı yordamı aslında burası, devam eden satırlarda bu nesnenin bir örneğinin oluş olmadığı sorgulanıyor
  // Yoksa hemen bir adet üretiliyor.
  // Varsa herhangi bir üretim söz konusu değil.
  if (FInstance = nil) then FInstance := TSingletonOrnegi.Create();

  // Sonrasında da sonuç değeri olarak örneğin kendisini dışarı veriyor...
  Result := FInstance;

  // Bunu da test etmeniz amacıyla ekledim, silebilirsiniz.
  ShowMessage('OnInstance');
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  // Doğru kullanımı bu şekilde...
  Tek := TSingletonOrnegi.GetInstance;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  VeyaBirBaskasi: TSingletonOrnegi;
begin
  // Aslında oluşturmayacak ve hiç bir tepki vermeyecek...
  Tek := TSingletonOrnegi.Create;

  // Bunu da oluşturmayacak ve yine tepki vermeyecek. Deneyin...
  VeyaBirBaskasi := TSingletonOrnegi.Create;
end;

end.

Bu olayı bir de görsel olarak incelemek gerekirse aşağıdaki animasyon sanırım iş görür…

 

Yorum yapılmamış

Yorumunuzu ekleyin