Statik Metotlu Sınıf mı Yoksa Singleton mı? – II
Bu konuyla ilgili ilk yazıda, yazılımlarımızda statik metotlara sahip sınıfı mı yoksa singleton (tek nesne) kalıbını mı tercih etmemiz gerektiğini sormuştum. Sorumu bir örnekle daha rahat düşünülebilir hale getireyim: “God” isimli bir sınıf oluşturmanız istendi sizden. Bu sınıfın üzerinde “createUniverse()” ya da “sendMessengers()” gibi metotlar olacağını biliyorsunuz. Bu durumda God sınıfını singleton yani tek nesneli sınıf mı yapardınız yoksa nesnesinin oluşturulmasına izin vermeyip, metotlarını statik mi yapardınız?
Bazı arkadaşlar güzel cevaplar verdiler. Onların cevaplarından da yararlanarak konuyu detaylıca cevaplandıralım.
İşin açıkçası ben şöyle düşünüyorum: Nesne-merkezli dillerde aslolan nesne olduğuna göre, statik metotlu sınıf yerine tek nesneli sınıfı yani singletonı tercih etmemiz gerekir. Zaten statik kullanımı mimlidir, olağan şüphelidir ve malesef çoğunlukla nesneye karşı konumlandırılmaktadır. Nesnelerin yönetimi ve çok bellek tüketmesi gibi sorunlar, henüz nesne kavramını anlamamış ama nesne-merkezli bir dili iyi bildiğini düşünenlerin dile getirdiği sebeplerdendir 🙂 Statik kullanımının nesneye tercihin sebebi, nesnenin gereksizliği değil olsa olsa nesnenin anlamsızlığı olabilir.
Yukarıda genel olarak ifade ettiğim bu tercihin pek çok teknik sebebi de var, sıralarsak:
- Singleton sınıfla bir miras hiyerarşisi kurup, metotlarını ezmeyi (override) tercih edebilirsiniz. Bu şekilde polymorphic davranış elde edebilirsiniz ki bu durum nesne-merkezli dillerin en temel özelliğidir. Fakat bu durum statik metotlar için geçerli değildir çünkü statik metotlar ezilemezler, sadece nesne metotları ezilebilirler. Bu yüzden polymorphic davranış elde etmek, örneğin “program to interface, not an implementation” gibi prensipleri uygulamak statik metotlu sınıflar ile mümkün değildir. Bir örnek vermek istersek, God sınıfını statik metotlu yaparsanız, örneğin farklı dinlere sahip olanlar, God sınıfından miras alan Allah, Yahweh vb. sınıflarlar oluşturup, üzerindeki “createUniverse()” gibi metotları ezemezler.
- Bu durum statik metotlu sınıf kullanımının en ciddi kısıtıdır. Bu yüzden sınıflarınızın miras mekanizmasıyla genişletilip (extension) genişletilmeyeceğinden emin olmalısınız. Örneğin java.lang paketindeki Math sınıfındaki metotlar statiktir ve zaten bu sınıf final yapılmıştır. Çünkü hem Math sınıfının nesnesinin oluşturulmaması istenmiş hem de genişletilmesinin önüne geçilmiştir. Aynı durum java.lang.System sınıfı için de geçerlidir. Fakat aynı paketteki Runtime sınıfı, genişletilebileceği düşüncesiyle statik metotlu değil tek nesneli yapılmıştır. Bu durumda farklı amaçlarla Runtime sınıfınıdan miras devralıp metotlarını ezebilirsiniz. Bu yüzden bu tip sınıflarda “getRuntime()” türünden (java.awt.Desktop sınıfında da getDesktop()) metotlar vardır. (Java SE API’sinde “getInstance()” metotlu belki 100den fazla sınıf var.”getInstance()” metoduna bakarak bunların tek nesneli sınıflar olduğunu düşünmeyin. Java SE API’sindeki statik “getInstance()” metotları çoğunlukla yapılandırıcı metot alternatifi olarak kullanılmıştır ve yeni bir nesne döner. Örneğin java.util.Calendar sınıfı.)
- Yukarıdaki maddeye çok benzer şekilde, singleton sınıflar, arayüzlerden türetilebilir dolayısıyla da devraldıkları metotlara kod sağlarlar. İlk maddedeki örnek, arayüz kullanımıyla da çözülebilir. Örneğin daha genel düşünüp, Creator isimli bir arayüz oluşurup, bunun God, Allah, Yahweh gibi sınıflarla genişletilmesini sağlamak daha güzel bir çözüm gibi görünüyor. Dolayısıyla “program to interface, not an implementation” prensibi uygulanmış olur.
- Singleton nesnenin oluşturulmasını, ihtiyaç noktasına kadar geciktirebilirsiniz. Bu durum sonradan yükleme (lazy loading) olarak adlandırılır. Fakat statik metotlu sınıflar için böye bir durum söz konusu değildir, sınıfın herhangi bir statik üyesine ulaşmanız halinde sınıf belleğe yüklenecektir. Sonradan yükleme aslen burada kısaca ele aldığımızdan çok daha derin bir konudur ve bu konuda daha ayrıntılı bilgi için buraya ve buraya bakabilirsiniz.
- Singleton kullanımı, özellikle ilk maddede belirttiğim nedenden dolayı Java dünyasındaki pek çok yapı tarafından statik metotlu sınıfa tercih edilir. Örneğin Spring ve Hibernate çatıları çok açık ve anlaşılır biçimde gerekmedikçe daima nesne kullanmayı tercih ederler. Spring, temel yapısı olan dependency injection (DI) mekanizmasını tamamen nesneler üzerine kurar ve nesnenin singleton olup olmadığını belirtmenize izin verir. Statik metotlu sınıfları ise tipik olarak “nesne üreten” yani factory olarak kullanmanıza izin verir.
- Statik kullanımı bulaşıcıdır. Yanlış duymadınız, statik kullanımını, felsefesine uyan yerlere sınırlanmadığınızda, yayılma eğiliminden dolayı bir müddet sonra sizi olur olmadık yerde statik kullanmaya itebilir. Bu durum da sizi sefil bir nesne programcısı yapabilir.
Statik metotlu sınıfın en temel artısı, nesne oluşturmak zorunda olmayışınızdır. Dolayısıyla bellek problemleri, nesnenin hayat döngüsünü yönetmek kodları vs. hep ortadan kalkacak, çalışma-zamanı performansınız ile ilgili daha az sıkıntınız olacaktır. Ama daha önce de bahsettiğimiz gibi, statik metotlu sınıflar yersiz kullanıldığında kodunuzu nesne-merkezli olmaktan çıkaracaktır.
Öte taraftan çok kanallı ortamlarda kullanılmaları durumda iki yapının da problemleri aynıdır, sonuçta tek bir kayak olduğundan, bu kaynakta eşzamanlı herhangi bir güncelleme sıkıntı yaratacaktır. Bu yüzden iki durumda da tedbir almak gerekecektir.
Yukarıdaki tartışmadan da anlaşıldığı gibi özellikle nesne yapılarından faydalanma ve genişletilebilme gibi özelliklerini kullanma söz konusuysa, tek de olsa nesne kullanımı, statik metotlu sınıf kullanımına tercih edilmelidir. Statik kullanımını olabildiğince nesne oluşturmanın anlamsız ve oluşturulan nesneyi ise sistemde gezdirmenin sıkıntılı olduğu durumlarla sınırlandırmamız gereklidir. Bu yazının devamı olarak buraya bakabilirsiniz çünkü bu yazıda, statik kullanımının nerelerde daha uygun olduğunu etraflıca açıklanmıştır.
Toplam görüntülenme sayısı: 1591