Nesne-merkezli diller bize nesne-merkezli kod yazmak için imkanlar sunmaktadır ama örneğin en azından nelerin nesne olacağı ve nesnelerin aralarındaki ilişkilerin nasıl olacağı gibi kararlar tamamen biz geliştiricilerin/tasarımcıların sorumluluğundadır. Bu sorumluluğu yerine getirmede sıkıntılar yaşadığımız müddetçe kullandığımız programlama dilinin bize sağlıklı bir nesne-merkezli yapı oluşturmak için yardım etmesi mümkün değildir.
Bir sınıf oluşturup, içine üç-beş tane private nesne değişkeni (instance variables) tanımlayıp, sonra da bu sınıfa set/get metotlarını koymak, kesinlikle kodunuzu nesne-merkezli yapmaz. Nesne-görünümlü yapabilir ama nesne-merkezli programlamanın, bundan daha fazla olduğunu söylememiz gereklidir.
GoF’un Kitaplarındaki şablonlar, doğru nesneleri bulup doğru bir şekilde kurgulamak için farklı tasarım seçenekleri sunar. Bunu da geliştirdiğimiz yazılımın daha rahat değişebilmesi için yaparlar. “Design for change” (degişiklik için tasarla), GoF’un kitabının ilk bölümündeki üç temel prensiptenn birisidir. “Program to an interface, not an implementation” (arayüze program yaz, gerçekleştirmeye değil) ve “favor object composition over class inheritance” (nesne bileşimini sınıf kalıtımına tercih et) kitabın ilk bölümünde savunulan diğer iki prensiptir.
1-Sınıflarda aşırı miktarda statik metot kullanımı
Kodunuzdaki statik metotların sayısı, kodunuzun ne kadar nesne-merkezli olduğunun göstergesidir. Hiç statik metota sahip olmayan bir yazılım düşünmek pek de mümkün değildir ama statik metotların kullanımının istisnai durumlardan çıkarak genel bir hal kazanması, yaklaşımınızın nesne-merkezli olmaktan çıktığının kanıtıdır.
Nesne-merkezli programlamada esas olan, nesne kullanmaktır; statik metotlar ise nesne oluşturmadan, sınıf üzerinden programlama yapmanın yoludur ve bu yüzden de nesne-merkezli programlamanın varlık sebebine karşı bir durum oluşturmaktadırlar. Dolayısıyla aslolan, nesne metotlarının (instance methods) kullanılması, statik metotların ise tamamen istisnai sebeplere bağlı olarak yazılmasıdır. Ne kadar çok statik metot kullanırsanız nesne-merkezli programlamadan o kadar çok uzaklaşıp prosedürel programlamaya o kadar yaklaşmış olursunuz.
Statik metot kullanıp nesne metotu kullanmamak, yazılımı geliştirirken nesneye ihtiyaç duymamak demektir. Bunun birkaç sebebi olabilir:
Metot hakikatten de nesnenin kendisine özel olan durumunu değil, nesnelerin ortak değerlerde barınan durumuna bağımlıdır. Bu durumda nesnelerin ortak durumu, statik değişkenlerle ifade edilir ve bu ortak duruma erişen metotların da statik olmaları normaldir. Bu anlamda statik metot kullanımı, olmadığında sıkıntılı ve verimsiz olacak halin sağlıklı bir şekilde çözülmesinde ibarettir. Örneğin Singleton tasarım şablonunda bir tane yaratılmış olan nesnenin “static” olması ve o nesneye ancak yine “static” olan bir metottan ulaşılması bir zorunluluktur.
Nesnesiz programlama yapmanın başka bir sebebi ise nesne oluşturmanın anlamsız olduğu durumlardır. Bu gibi hallerde durum da vardır ve bu durum üzerinde çalışan metotlar da vardır ama durum ve metotların üzerinde tanımlandığı yapının nesne olması konusunda çok da açıklık yoktur. Böyle hallerde sınıfın kendisinin nesne olarak davranmasını bekleriz. Örneğin “Tanrı” diye bir sınıfımız olsa bu sınıftan kaç tane nesne üretmeyi düşünürdünüz? Eğer çok tanrılı bir dine inanmıyorsak, olsa olsa bir tane Tanrı nesnesi üretmek yeterli olurdu. Bu durumda hiç nesne oluşturmadan Tanrı sınıfını nesne gibi kullanmak dolayısıyla da bu sınıfın üzerindeki her türlü durumu ve davranışı statik yapmak son derece anlamlı olur.
Dolayısıyla bazen ya kavram o kadar soyuttur ki gerçekte bir nesne ile ifade etmezsiniz ve sınıfı nesne olarak kullanırsınız ya da kavramınızın verdiği hizmetler hiç bir şekilde o sınıftan üretilecek nesnelerin farklı olacak durunmlarına bağlı değildir. Java APIsindeki java.lang.Math sınıfı buna çok güzel bir örnektir. Math sınıfının nesnesini oluşturmanın anlamı yoktur, çünkü onun sınıfını yukarıdaki Tanrı sınıfında olduğu gibi olsa olsa ancak bir tane nesneye sahip olabilir (eğer tek bir evrenimiz varsa ve evrenin her yerinde aynı Matematik geçerliyse) bu yüzden sınıfını nesne olarak kullanabiliriz. Ayrıca Math sınıfının metotlarının davranışı bu sınıftan oluşturulacak nesnelerin durumuna bağlı değildir. Dolayısıyla Math sınıfından nesne oluşturmadan üzerindeki statik metotlar sayesinde ondan hizmet alabiliriz.
Bazen, gereksiz nesne oluşturmama gibi masum bir amacın, bol statik metot kullanımına yol açtığı görülmektedir. Gereksiz nesne oluşturma, belleğin gereksiz kullanımına dolayısıyla da, bellek kaçağı (memory leak), performans ve ölçeklenirlik (scalability) problemlerine yol açar. Ama bu durumun tedavisi “hiç nesne oluşturmamak” değildir. Yani “nesneler belleği şişiriyor dolayısıyla nesne oluşturmayıp statik metotlarla çalışıyoruz” demek daha büyük sorunlara yol açacaktır. Eğer hakikatten bellek kullanımı bu kadar önemli ve kritik ise, bunun mimari bir ihtiyaç olarak ortaya konması ve bu seviyede çözümünün aranması daha doğru bir yaklaşım olur. Böyle bir çalışma, nesne merkezli bir dil kullanmak yerine bellek konusunda daha cimri olan prosedürel bir dil kullanmak gibi bir karar ile de sonlanabilir. Ya da Java kullanarak ve bellek konusunda daha tedbirli davranarak da bu sorunu çözebilirsiniz. Nesne oluşturmaktan kaçınmanın ana sebebi nesnenin “gereksiz” değil olsa olsa “anlamsız” olmasıdır. Yukarıda bahsedilen Tanrı ya da java.lang.Math sınıfı gibi, modellenen işin bir parçası olmayan, dolayısıyla işle ilgili nesneden nesneye değişen bir duruma sahip olmayan, araçsal sınıflar ve genelde onların statik olan metotları olmasaydı (ve dolayısıyla da biz bu sınıfların metotlarını ancak nesneleri üzerinden kullanabilir olsaydık,) bellek probleminden çok daha önce kod yapımızla ilgili problemler yaşardık. Çünkü bu gibi sınıfların nesnelerini yaratmak çoğu zaman çok fazla bilgi gerektirir ve bu durumu aşmak için nesne üretici (factory) yapılar gibi başka tasarım desenlerine ihtiyaç duyardık.
- Nesne yaratmanın anlamsız olduğu durumlar. Bu durumlarda sınıfı, nesne olarak kullanabilirsiniz. Nesne yaratılsa bile sadece bir nesneye ihtiyaç varsa ki bu durum Singleton tasarım şablonuna karşılık gelir, sınıfın kendisini o tek nesne yerine, sanki nesneymiş gibi kullanılabilir
- Metodunuz, üzerinde çağrılacağı nesnenin hiç bir nesne değişkenini (instance variable) kullanmıyorsa, yani nesnenin durumuna ulaşmıyorsa, statik metot kullanabilirsiniz. Çünkü statik metotlar statik olmayan değişkenlere ulaşamazlar.
- Metodun çağrılması için bir nesneye ihtiyacınız yoksa statik metot kullanabilirsiniz. Özellikle aynı nesnenin pek çok farklı bağlama geçilip, hepsinde kullanılması gibi durumlarda, farklı bağlama nesne geçmek yerine orada doğrudan sınıf üzerindeki statik metotları çağırmak son derece kolaylıktır. Özellikle araçsal davranışlara sahip (utility) sınıfları bu şekilde yazılımın her bölgesinde kullanmak anlamlıdır.
Statik metotlara sahip böyle bir sınıfı kullanmanın kolaylığı çok açıktır: Sınıfın kendisini nesne gibi kullanmak. Dahası o tek nesneyi herhangi bir yere geçmenize bile gerek yok, doğrudan sınıfın üzerinden statik alanlara ve metotlara ulaşabilirsiniz.
Eğer yukarıdaki Log sınıfının metotlarını statik yapmak istemezsek ve bu sınıfın sadece bir tane nesnesinin olacağını planlıyorsak bu durumda Log sınıfını singleton olarak şöyle kodlardık:
- 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.
- Static metotlarda ki bir diger problem de unit test yazarken mock objelerde ortaya cikiyor.
Bu yüzden static metotlarin ufak utility class larinda yazilmasi daha doğru olacaktır.