Tasarım Kalıpları: Singleton (Tek Nesne) – I
Tasarım kalıpları yazı serisine, ilk kalıp örneğimiz ile devam edebiliriz.
Singleton, İngilizce’de tek olan şeye verilen isimdir. Yalnız yaşayan kişiye ya da biriç gibi oyunlarda, oyunun başında elde olan bir renkten tek kağıda da singleton denir. Yazılım açısından ise singleton, bir sınıftan sadece bir nesneye sahip olmak anlamına gelir. Bu yüzden bu kalıba dilimizde “tek nesne” diyebiliriz.
Singleton kalıbı, GoF’un nesne yaratmayla ilgili “creational” kalıplarındandır. GoF singleton için şu amaç (intent) ifadesine sahiptir:
Ensure a class only has one instance, and provide a global point of access to it.
Ya da
Bir sınıfın sadece bir tane nesnesinin olduğundan emin ol ve ona global bir erişim noktası sağla.
Singleton, yazılımlarda sıklıkla karşımıza çıkan ve bir sınıftan sadece ve sadece bir tane nesnenin bulunması gereken durumlara bir çözümdür. Bir sınıftan bir tane nesne olmasından kasıt ise, herkesin istediği zaman bu sınıfın bir nesnesini oluşturmaya çalışmamasıdır, oluşturamamasıdır. “Bir sınıfın sadece bir tane nesnesinin olduğundan emin ol” ile kastedilen budur.
Böyle bir durumla çok sık karşılaşırız. Arka taraftaki sisteme erişimleri kontrol eden yapıdan sadece bir tane olması istenir. Çünkü, bu tür nesneler trafik polisi gibi geçişi kontrol ederler, arka taraftaki sistemin vereceği hizmetlerin alınmasını düzenlerler vs. Örneğin, bir sistemde var olan yazıcıların, merkezi bir yerden yönetilmeleri makul bir durumdur. Kim hangi yazıcıya erişebilir, kim hangi yazıcıdan iş istiyor, bir yazıcıdan istenen işlerin sıraya konulması ve farklı tipte kullanıcıların farklı yazıcılarda farklı basım haklarına ve sayısına sahip olması gibi ihtiyaçların olduğu durumlarda, tüm yazıcıları ve onlara gönderilen basım isteklerini yönetecek bir merkezi sistemin (print spooler gibi) olması kaçınılmazdır. Bu sistemde merkezde bulunup, bu işleri yapacak olan nesneden sadece bir tane olması istenir. Hiç bir istemci, var olan tek nesne dışında, ikinci bir nesne yaratamamalıdır.
Benzer durum, yogun veri tabanı iletişimi bulunan OLTP tipindeki sistemlerde de veri tabanı erişim yönetiminde ortaya çıkar. Veri tabanına erişmek isteyen uygulamanın, erişimi sağlayan bağlantı (örneğin java.sql.Connection) nesnesine sahip olması gereklidir. Ama uygulamanın veri tabanına erişecek kısımlarının hepsinin ayrı ayrı bağlantı oluşturması istenen bir durum değildir. Çünkü hem bağlantı oluşturmak merkezileştirilmeli hem de bağlantı oluşturmak pahalı bir iş olduğundan, bağlantılar önceden oluşturulup bir havuzda (connection pool) tutulmalıdır ki isteyene bu havuzdaki hazır bağlantılardan servis yapılabilsin. Bu yüzden, bağlantıların üretilmesi, bir havuza atılması, isteyenlere servis edilmesi, istemcilerin kullandıkları bağlantılarla işleri bittikten sonra bağlantıyı havuza geri döndürmesi vb. gibi, tüm uygulamanın bağlantılarla ilgili ihtiyaçlarının merkezi bir yerden halledilmesi, mimari olarak uygun hatta gerekli bir yöntemdir. Bu durumda da bu merkezi konumdaki sınıfın, ismi ConnectionManager olabilir örneğin, sadece ve sadece bir tane nesnesinin olması gereklidir. Birden fazla nesne olması durumunda veri tabanına erişim ile ilgili ciddi problemler ve tutarsızlıklar olacağı açıktır.
Diğer taraftan, singleton yani tek olan nesneye erişim problemi de olmamalıdır. Kalıbın amacındaki “ve ona global bir erişim noktası sağla.” ifadesi, bu erişimin her yerden yapılabilecek şekilde olması gerektiğini söylemektedir.
Şimdi bu kalıbın çözümüne geçelim. Çözümde ben Java’yı kullanacağım ve bu çözüm, aslında kalıp olarak çok basit olmasına rağmen, detay bazı noktalardan dolayı bir yazı da gerektirecek cinstendir.
Peki şimdi son derece analitik olarak yaklaşıp, “nesne yaratma”ya odaklanalım. Çünkü kalıp, nesne yaratmakla daha doğrusu nesne yaratmayı sınırlandırmakla ilgili. Java’da nesne yaratmak için (iki sınıf hariç) daima “new” anahtar kelimesini kullanır ve yapılandırıcı (constructor) çağrısı yaparız. Yani singleton olacak sınıfın ismi Singleton ise, bu sınıfın nesnesini aşağıdaki gibi yaratırız:
... Singleton object = new Singleton(); ...
Singleton sınıfından nesne oluşturmanın ifadesi yukarıdaki satır olduğuna göre, biz bu satırın çağrılmasını nasıl kısıtlarız? Yani bu satırın herhangi bir istemci sınıfta yazılmasını nasıl engelleriz? Kalıbın çözümündeki esas mekanizma bu soruya verilecek cevap ile ortaya çıkar. Çünkü herhangi bir istemci sınıf bu ifadeyi yazabilirse, çok rahatlıkla bir for döngüsü içerisinde bu sınıftan bir sürü nesne oluşturabilir. Yani, öncelikle bu sınıftan tek bir nesne oluşturulmasına değil, istemcilerin hiç nesne oluşturamamasına odaklanmamız gereklidir. Bu da ancak ve ancak yukarıdaki satırın bir istemcide yazılmasının önüne geçmekle olabilir. Dolayısıyla, Singleton sınıfının dışında herhangi bir yerde, “Singleton object = new Singleton()” yazılmasının önüne nasıl geçebiliriz?
Soru nefis değil mi? Bu gibi bulmaca cinsinden sorular, kalıpların içinde çok sıklıkla ortaya çıkar ve tam da bu yüzden kalıpları öğrenmek çok zevklidir, çünkü zekanızı zorlarsınız.
Bakın, analitik yani çözümlemeci olmanın dibine vuralım ve sırayla gidelim. Bu cümlede
- İlk element olan “Singleton”ın yazılmamasını sağlamanın yolu bu sınıfı public yapmamaktır. Singleton sınıfı public olmazsa, hiç bir erişim niteleyici (access modifier) kullanılmaz ve bu yüzden de bu sınıf, varsayılan erişime (default access) sahip olur. Bu durumda bu sınıfa sadece ve sadece içinde bulunduğu paketten erişilebilir. İstediğimiz ise bu değildir, çünkü bu durum global erişimi engelleyen bir haldir. Dolayısıyla çözüm, Singleton sınıfını public yapmamak değildir. Bu yüzden istemcilerde “Singleton” kelimesinin yazılmasını önleyemeyiz.
- İkinci element olan “object”, üçüncü element olan “=”, atama işlemcisi ve dördüncü element ve bir anahtar kelime olan “new” da yazılmaları engellenemeyecek olan elementler değillerdir. Bir istemcide bu üç elementi de rahatlıkla yazabilirsiniz. Yani çözüm burada da değildir.
- Geriye yazımını kontrol edebileceğimiz sadece ve sadece beşinci element olan “Singleton()” yapılandırıcı çağrısı kalmaktadır. Yukarıdaki cümlede, Singleton sınıfı dışında herhangi bir yerde yazılmasını engelleyebileceğiniz tek kısım “Singleton()” çağrısıdır. Çünkü Java’da erişim sınırlaması sağlayan yapılar (access modifiers) temelde üye elementlere erişimi kontrol ederler. Üye elementler ise ya üye değişkenler ya da üye metotlardır. Yapılandırıcı da üye bir metot olarak bu erişim kısıtlamasına dahildir. Uzun lafın kısası, yapılandırıcıyı “private” olarak nitelerseniz, kimse “Singleton()” yapılandırıcı çağrısını yapamaz.
Bu durumda Singleton sınıfımızı şöyle tanımlayabiliriz demektir:
public class Singleton{ private Singleton(){ System.out.println("Creating a singleton object"); } }
Yukarıdaki gibi bir kod daha önce hiç görmüş müydünüz? Garip olan şey, kaş yapayım derken göz çıkarmış olmamızdır 🙂 Yani böyle bir sınıfın hiç bir nesnesini kimse oluşturamaz, değil mi? Değil, doğrusu, “yukarıdaki Singleton sınıfının nesnesini, Singleton sınıfı dışında kimse oluşturamaz” şeklindedir. Yani Singleton, kendi nesnesini oluşturabilir çünkü “private” erişim niteleyicisi, kendi sınıfı için geçerli değildir. Bu durumda sınıfımız şu hale gelecektir:
public class Singleton{ private Singleton object = new Singleton(); private Singleton(){ System.out.println("Creating a singleton object"); } }
Yukarıdaki kodu görünce ne düşündünüz? Böyle bir kod ile nesne kavramını ne kadar iyi bildiğinizi sınayabilirsiniz. Çünkü bu kod, “imkansız” koddur, fasit dairedir, hiç bir işe yaramaz. Çünkü singleton bir nesnenin, kendi tipinden bir nesne değişkeni olamaz. Bu yüzden “private Singleton object = new Singleton();” ifadesi bu haliyle saçma bir durum oluşturmaktadır. Düşünün öyle bir nesne oluşturacaksınız ki, içinde kendi tipinde bir başka nesne (yukarıdaki örnekte “object” ile ifade edilen) olacak ve bu içindeki nesne daha önce oluşturulmuş olacak ki yeni oluşan nesnenin parçası olabilsin. Bu mümkün değildir çünkü yukarıdaki yapıya göre Singleton sınıfının nesnesi zaten Singleton sınıfı dışında bir yerde oluşturulamaz. Buradan şu sonuç çıkar, “object” ile referans verdiğimiz nesne, nesne değişkeni değil, sınıf değişkeni, yani “static” olmalıdır. Bu durumda sınıfımızın yeni hali şöyle olur:
public class Singleton{ private static Singleton object = new Singleton(); private Singleton(){ System.out.println("Creating a singleton object"); } }
Sınıfımızın bu son hali, kalıbın amacının “Bir sınıfın sadece bir tane nesnesinin olduğundan emin ol ” kısmını karşılamaktadır. Kalıbın amacının ikinci kısmı olan “ve ona global bir erişim noktası sağla.” ise bu sınıfa konulacak ve oluşturulan “object”i servis edecek public ve static bir metotla halledilebilir. Bu durumda da sınıfımız şu hale gelmiş olur:
public class Singleton{ private static Singleton object = new Singleton(); private Singleton(){ System.out.println("Creating a singleton object"); } public static Singleton getInstance(){ return object; } }
Artık Singleton sınıfımız gerçekten tasarım kalıbının istediği gibi tek bir nesnesi olan ve bu nesneye global bir erişim sağlayan yapıdadır.
Yukarıdaki Singleton sınıfını, sadece tek bir nesnesinin oluşturulduğunu ispatlayacak şekilde değiştirirsek:
package org.javaturk.dp.pattern.gof.creational.singleton; public class Singleton { private static Singleton singleton = new Singleton(); private static int count; private String name; private Singleton() { count++; name = "Singleton" + count; } public static Singleton getInstance() { return singleton; } public void printName() { System.out.println(name); } }
Bu sınıfın bir de istemcisini de yazarsak:
package org.javaturk.dp.pattern.gof.creational.singleton; public class SingletonClient { public static void main(String[] args) { for (int i = 0; i<10; i++){ Singleton s = Singleton.getInstance(); s.printName(); } } }
ve bu istemciyi, SingletonClient, çalışırırsak, göreceksiniz ki program konsola 10 tane “Singleton1” yazacaktır. Çünkü sadece bir nesne oluşturulmuştur çünkü o tek nesnenin ismi “Singleton1″dir.
Singleton tasarım kalıbıyla ilgili daha geniş bilgi için hem GoF kitabına hem de c2.com’a bakabilirsiniz.
Singleton tasarım kalıbı üzerine daha pek çok şey söylenebilir. Bunları da bir sonraki yazıya bırakalım.
Toplam görüntülenme sayısı: 7107