Singleton Design Pattern (Singlethread & Multithread & Static Initialization)

Singleton design patternde Factory pattern gibi bir creational pattern. Yani aynı şekilde nesne yaratmak amacıyla var olan bir pattern.

   

Bazı nesnelerin application boyunca yaşamasını ve bir başka örneğinin yaratılmamasını isteriz bu gibi durumlarda singleton design pattern bizim için kullanışlı olacaktır.

   

Örneğin bir socket bağlantınız var ve uygulama boyunca açık olan bu bağlantı üzerinden haberleşmek istiyorsunuz veya aynı durum database içinde geçerli olabilir. SocketManager diye bir class'imiz olduğunu düşünün ve bunun bir örneği olsun istiyoruz. Aşağıdaki kod ile devam edelim.

   

   

Kod içinde komutlarda olabildiğince aciklamaya çalıştım. Böylece sürekli olarak yeni bir nesne yaratmak yerine her defasında aynı nesneyi kullanmış oluyoruz.

   

   

Yukarıdaki kod birden cok thread kullanımında güvenli değil çünkü iki thread aynı anda birden cok instance yaratmış olabilir bunu önlemek için Double check locking pattern kullanmalıyız. Bu pattern ile yukarıdaki kodu birden çok threadin mantıklı şekilde kullanmasını sağlayabilriiz. Diğer bir yöntem ise Static initialization.

   

Multithread Singleton

   

Birden çok thread kullandığımızı düşünelim. Threadlerden biri if (activeConnection == null) kontrolunu yaptı, sonuc olarak true aldı ve nesne yaratacak tam bu sırada (nesne yaratılmadan önce null check yapıldıktan sonra) OS ikinci thread'I devreye aldı ve ikinci thread'de if (activeConnection == null) kontrolunu yaptı aynı şekilde bu thread'de sonuc olarak true aldı ve nesne yaratmaya karar verdi. Bu durumda gördüğünüz gibi iki thread ayrı ayrı iki nesne yaratmış oldu. Yani activeConnection = new SocketManager(); kodu birden çok defa çalıştı. Buda Singleton mantığına uymayacak bir yapı oldu. Bunun için C#'da bulunan lock keywordunu kullanarak thread safe bir hale getirebiliriz.

   

   

Yukarıda görüdğünüz gibi ilk olarak bir object yaratıyoruz, bu objectin amacı lock keywordu içerisinde kullanılacak nesneyi tanımlamak.

   

Daha sonra lock(syncRoot) kullanarak scope içerisine aynı anda sadece bir thread girmesine izin veriyoruz. Bu scope'a giren thread işini bitirip scope'u terk etmeden diğer thread'ler lock keywordunde bekliyor.

   

Yaptık mı?

   

Evet artık singleton oldu ama kodu daha güzelleştirmemiz mümkün. Neden?

Nesne yaratılmasındaki sorunu çözdük ama şimdide lock bizim için bir sorun birden cok thread aynı anda lock'lu bölüme ulaşacağı zaman birbirlerini bekleyecekler aslında bu bizim istediğimiz birşey ama her zaman değil, nesne yaratılana kadar durumun böyle olması gerekiyor. Ama sonrasında artık lock'a ihtiyacımız yok, tamda burada double-checkin locking pattern devreye giriyor. Bu pattern sayesinde lock'a girmeden önce bir null check daha yapmalıyız. Yani kod aşağıdaki gibi oluyor.

   

   

Şimdi koda baktığımız zaman ilk önce if (activeConnection == null) yapıyoruz eğer değişken null ise ve yaratılacağını düşünüyorsak lock yapıyoruz ve lock içerisinde tekrar check ediyoruz. Eğer değişken hala null ise bu sefer nesne yaratıyoruz. Böylece eğer nesne zaten yaratılmış ise ilk if'i geçemeyeceği için lock'a girmeyecek ve threadler birbirini her defasında beklemeyecek, sadece activeConnection null ise lock devrede olacak, bu patternde Double-Check Locking pattern. http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

   

Static Initialization

   

Diğer bir yol ise static initialization, nesne yaratma olayını developer'a değil run-time'a devrediyoruz. Böylece static ve readonly olarak yaratılan activeConnection değişkeni uygulama açıldığında bir kez yaratılacak ve sonra istense bile readonly olduğu için değiştirlemeyecek. Böylece multithread'e uygun hale gelecek

   

   


Kodlara aşağıdaki linklerden ulaşabilirsiniz.

https://github.com/altinokdarici/DesignPatterns/tree/master/CreationalPatterns/SingletonPattern   

https://github.com/altinokdarici/DesignPatterns/tree/master/CreationalPatterns/MultithreadSingletonPattern

https://github.com/altinokdarici/DesignPatterns/tree/master/CreationalPatterns/StaticInitializationSingletonPattern

   

Deniz Demircioğlu'na bana kattıkları için özellikle teşekkür ederim.

Factory Design Pattern

Factory pattern design patternler arasında en başta gelenlerden biri olması sebebiyle buradan başlayıp ileride bahsedebildiğim kadar çok design pattern'den bahsetmeye çalışacağım.

   

Factory pattern adındanda anlaşılacağı gibi bir creational pattern (diğer örnekleri; abstract factory, builder, stereotype etc.), yani nesne yaratmamızı ve yaratılacak nesneyi seçememize imkan veriyor. Gerçek hayata uygun bir senaryo ile konuyu anlatmaya çalışacağım.

   

Yukarıda gördüğünüz diagramda iki büyük yuvarlak var bunları Kırmızı: UI - Client , Yeşil: Logic olarak düşünelim. Clientimiz ekrana şekil çizdermek istiyor bunun için Rectange, Triangle, Circle class'larinin birinden nesne yaratmalı ve draw methodunu çağırmalı. Draw() methodunun IShape interface'I içinde olduğunu düşünelim. Triangle, Rectangle ve Circle class'lari Ishape'I implement etmiş ve Draw() methodunu içeriyor. Client basit bir şekilde Triangle t = new Triangle(); t.Draw(); şeklinde bir komut ile Triangle çizdirebilir. Ancak yarın eklenecek yeni bir şeklin çizdirilmesi için client kodunda değişiklik gerekir. Bu sorunu önlemek için client sadece IShape interface'ini ve Factory class'ini bilir. Factory class'I tüm Triangle Rectangle Circle gibi classlari bilir. Client, factory üzerinden create komutu ile beraber istediği türün ismini veya key değerini parametre olarak vererek factorynin bir nesne yaratmasını ister. Factory içinde yaratılan tüm nesneler Ishape'I implement ettiği için; factory, client'a IShape türünde bir nesne gönderir ve Client gelen nesnenin Ishape olduğu için Draw(); methodunu içerdiğini bilir, böylece Draw(); methodunu çağırarak ekrana yazdırabilir.

   

Bu sayede gelecekte yarataılacak diğer class'lar için client kodunda değişiklik gerekmez örneğin ileride Ellipse isimli bir class yaratmak istersek class'I yaratıp factory içinde değişiklik yapmamız yeterli olacak. Ellipse bir IShape olduğu için sorun çıkmadan ve çok az kod değişikliği ile sisteme eklenebilir. Client tarafında hiç bir ekstra kod yazmaya gerek kalmadan sisteme ekleme yapılmış olur.

   

Kodlara bakalım

   

   

Örneğin Ellipse class'ini eklersek aşağıdaki kod'a ulaşmış olacağız. Gördüğünüz gibi yeni bir class eklemek dışında tek değişiklik Factory üzerinde yapıldı ve Client'ta hiç bir değişiklik gerekmedi.

   

   

Bonus Part (Reflection)

   

Peki bu tip değişikliklerin sadece class eklemek ile tamamlanmasını ve Factory üzerinde kod değişikliği yapmak istemezsek nasıl yapabiliriz? OOP gereği bu yöntemi ben pek tercih etmesemde, c#'in (java etc. dillerde aynı özellikler mevcut) reflection ile bu işi yapabiliriz. Aşağıda gödüğünüz kod ile artık her eklenen IShape implement etmiş class otomatik olarak client tarafından kullanılabilir hale gelmektedir.

   

   

Yukarıda göreceğiniz. "FactoryPattern". + typeName ile nesne yaratmak istediğimiz class adını ifade ediyoruz. "FactoryPattern." yazmamın sebebi benim application'imin default namespace'I "FactoryPattern" ve yeni oluşturulacak IShape implement eden class'in FactoryPattern namespace'inde olduğunu varsayıyorum.


Kodlara aşağıdaki linkten ulaşabilirsiniz.


https://github.com/altinokdarici/DesignPatterns