Etkin Java (Effective Java) — 1

Madde 1. Yapılandırıcılar Yerine Statik Fabrika Metodlarını Düşünün

Bir sınıf için örneğini oluşturmanın normal yolu, public(herkese açık) yapılandırıcılar(constructor) kullanmaktır. Her programcının alet çantasında olması gereken başka bir yöntem daha vardır. Bir sınıf public statik fabrika metodları sağlayabilir ve bunun aracılığıyla sınıfının örneğini oluşturabilir.

Boolean sınıfından bir örnek. Bu metod, boolean ilkel değişken değerini Boolean nesne referansına çeviriyor.

Not olarak, statik fabrika metodları ile Tasarım Modellerinden Fabrika Metod modelinin aynı şeyler olmadığını belirtelim.

Statik fabrika metodları sunmanın hem avantajları hem de dezavantajları vardır.

Statik fabrika metodlarının bir avantajı, yapılandırıcıların aksine, bir isimlerin olmasıdır. Eğer yapılandırıcılara geçilen parametreler dönen nesne ile ilgili güzel bir tanımlama sunmuyorsa, iyi-seçilmiş isme sahip olan statik fabrika metodlarının kullanılması ve okunması çok daha kolay olur. Örneğin, BigInteger(int, int, Random) yapılandırıcı, muhtemelen değeri asal olan BigInteger nesnesi dönüyor. Eğer BigInteger.probablePrime isimli statik fabrika metodu olsaydı daha iyi anlaşılabilirdi(Java 1.4 sürümü ile bu statik metod eklenmiştir).

Bir sınıf belirli bir parametre imzası ile tek bir yapılandırıcısı olabilir. Programcılar bunu aşabilmek için birbirine benzer iki yapılandırıcıyı, bunların sadece parametrelerinin sırasını değiştirerek, tanımlama yoluna giderler. Bu gerçekten kötü bir fikirdir. Böyle bir kütüphaneyi kullanan bir kullanıcı, hangi yapılandırıcının hangisi olduğunu hiçbir zaman hatırlayamaz ve büyük ihtimalle yanlış olanını çağırırlar. Genellikle, kodu okuyan insanlar, sınıf dökümantasyonuna bakmadan kodun ne yaptığını anlayamazlar.

Statik fabrika metodlarının isimleri olduğu için, böyle bir kısıtlamaları yoktur. İsimleri iyi seçilmiş fabrika metodları ile farklılıklar açıkça ortaya konmuş olur.

Statik fabrika metodlarının ikinci avantajı da, yapılandırıcıların aksine, her çağrıldıklarında yeni bir nesne oluşturmak zorunda değillerdir. Bu durum, öntanımlı örnekleri kullanmak için değişmez(immutable) sınıflar kullanmamızı, örnekleri oluşturup önbelleğe almamızı, gereksiz nesnelerin oluşmasından sakınmamızı sağlar. Boolean.valueOf(boolean) metodu bu tekniği kullanır, metod asla nesne oluşturmaz. Bunu yapmamız, eşdeğer nesneler çokça çağrıldıkları zaman, bize büyük performans artışı sağlar. Özellikle de nesneyi üretmenin pahalı olduğu durumlarda.

Tekrarlı çağırımlarda statik fabrika metodlarının aynı nesneyi dönebilmesi, herhangi bir zamanda sınıf örneklerinin neler olduğunu sıkı şekilde kontrol edebilmemizi sağlar. Bunu yapan sınıflara, örnek-kontrollü(instance-controlled) sınıflar denir. Örnek-kontrollü sınıflar yazmanın birçok sebebi olabilir. Sınıfın tekiz(singleton) olmasını ya da örneğinin oluşmamasını garanti eder. Değişmez(immutable) sınıflar oluşturmamızı sağlar, böylece a.equals(b) yerine a==b kullanabiliriz. Bu da büyük performans artışı sağlar. Enum tipleri bunu garanti eder, yani Enum tiplerinde a.equals(b) yerine direkt a==b ile kontrol edebiliriz.

Statik fabrika metodlarının üçüncü avantajı ise, yapılandırıcıların aksine, sadece dönüş tiplerini değil, dönüş tiplerinin alt-tiplerini dahi nesne olarak dönebilir. Bu size döneceğiniz sınıfın seçiminde büyük bir esneklik sağlar.

Bu esnekliğin bir uygulaması da, oluşturduğunuz API, sınıflarını public yapmadan nesneleri döndürmenizi sağlar. Sınıfların gerçekleme detaylarını saklamak, kısa ve etkili API’ler oluşturmamızı sağlar. Bu teknik bize arabirim(interface) tabanlı çatılar(frameworks) oluşturmamızı sağlar. Bu çatıda statik fabrika metodları için, arabirimler dönüş tiplerini oluşturur.

Arabirimler statik metodlara sahip olamazlar, geleneksel olarak, Type isimli arabirim için olan statik fabrika metodları, örneklenemeyen(noninstantiable) Types isimli bir sınıf içine konulurlar.

Örneğin, Java Collections Çatısı kendi koleksiyon arabirimleri için 32 tane kullanışlı sınıf gerçeklemesi(implementation) sunar. Bunlar değişmeyen(unmodifiable) koleksiyonlar, senkronize edilmiş(synchronized) koleksiyonlar, vb. gibidirler. Neredeyse bütün bu gerçeklemeler statik fabrika metodları aracılığıyla dışarı açılırlar ve örneklenemeyen bir sınıf(java.util.Collections) içerisindedirler. Bu sınıflardan dönen nesnelerin hiçbiri public değildir.

Eğer 32 tane ayrı sınıf public olarak dışarı açılmış olsaydı, bu sınıflar Collections Çatısı API’den çok daha büyük bir küme olmuş olurdu. Sadece API’nin kod bakımından büyüklüğü değil, kavramsal büyüklüğü de çok olurdu. Üstelik sınıfların gerçeklenmiş halleri yerine arabirimlerini dönen statik fabrika metodları kullanmak, genel olarak iyi bir pratik olmuştur.

Statik fabrika metodlarından dönen nesnenin sınıfını sadece gizlemekle kalınmaz, ayrıca metodun farklı zamanlarda değişik değerlerle çağrımına göre de farklı sınıf nesneleri dönebiliriz. Bildirilmiş dönüş tipinin her alt-tipini yeni dönüş tipi olarak kullanabiliriz.

java.util.EnumSet sınıfının, Java 1.5 ile geldi, public yapılandırıcısı yoktur, sadece statik fabrika metodları vardır. Dönüş tipi altında sahip olduğu enum tipinin boyutuna göre 2 sınıftan biri olabilir. Eğer çoğu enum tipinde olduğu gibi, 64 ve daha az boyutlu eleman kümesine sahipse, statik fabrika metodu RegularEnumSet örneği dönüyor, ki bu da tek bir long tarafından destekleniyor. 65 veya daha fazla olduğu durumda da, fabrika metodu JumboEnumSet örneği dönüyor, ki bu da long dizisi tarafından destekleniyor.

Bu iki sınıf gerçeklemesinin varlığından kullanıcıların haberi olmaz. Eğer ilerde RegularEnumSet, küçük enum tipleriyle alakalı bir performans kaybına yol açarsa, herhangi bir sıkıntı çıkmadan gelecek sürümlerde koddan çıkarılabilir. Benzer olarak, performansa katkısı olacağı düşünülürse, gelecek sürümler de belki üçüncü veya dördüncü bir EnumSet gerçeklemesini eklenebilir. Sizin bilmeniz gereken tek şey, yeni eklenen sınıf EnumSet sınıfının bir alt-tipi olacağıdır.

Statik fabrika metodundan dönen nesnenin sınıfı, metod yazılırken var olmak zorunda da değildir. Böyle esnek statik fabrika metodları servis sağlayıcı çatılarının(service provider frameworks) temelini oluşturur, örneğin Java Database Connectivity API(JDBC). Servis sağlayıcı çatı, bir sistemdir, bu sistemde birçok servis sağlayıcı bir servisi gerçekler ve sistem bu servisleri kullanıcıya, detaylarını bilmesine gerek kalmadan, kullanması için açar.

Bir servis sağlayıcı çatısının üç ana bileşeni vardır. Servis arabirimi(interface), sağlayıcının gerçeklediği yapılardır. Sağlayıcı kayıt(provider registration) API, sistem bunu kullanarak gerçeklenen arabirimlere kayıt olur ve kullanıcıların buna ulaşmasına izin verir. Servis erişim(access) API, kullanıcılar servisin nesnesine ulaşmasını sağlar. Servis erişim API, genel olarak kullanıcının bir sağlayıcı seçimine izin verir ama zorlamaz. Sağlayıcının olmadığı durumlarda, API varsayılan sağlayıcının örneğini döner. Servis erişim API, esnek bir statik fabrika metodudur ve servis sağlayıcı çatıların temelini oluşturur.

Opsiyonel dördüncü bileşen de, servis sağlayıcı arabirimidir(service provider interface). Bu da oluşturulacak servis örneği için bir arabirim sunar. Servis sağlayıcı arabirimlerin yokluğunda, gerçeklenen servisler sınıfın adıyla kayıt edilir ve otomatik olarak ilk değerleri set edilir. JDBC örneğine bakarsak, Connection servis arabirimi olarak görev alır. DriverManager.registerDriver ise sağlayıcı kayıt API, DriverManager.getConnection ise servis sağlayıcı API ve Driver ise servis sağlayıcı arabirimidir.

Birçok farklı servis sağlayıcı çatı modeli vardır. Örneğin, Adapter Modeli ile olanına bakalım.

Statik fabrika metodlarının dördüncü avantajı ise parametreli tip örneği oluşturmak için gereken kalabaklığı azaltmasıdır(Java’nın yeni sürümlerde bu konuda kolaylaştırıcı adımlar oldu). Genelde tip parametresini iki kere tanımlarsınız,

Parametrelerin karmaşıklığı artarken böyle tanımlamalar yapmak da zorlaşır. Statik fabrikalar ile tip çıkarımı(type inference) yapabiliriz.

Sonra da üstte yaptığımız ifadeyi daha özlü yazabiliriz.

İ.K. Notu: Java 7 ile beraber artık şöyle tip çıkarımları yapabiliriz.

Map<String, List<String>> m= new HashMap<>();

Sadece statik fabrika metodları tanımlamanın başlıca dezavantajı, public veya protected yapılandırıcı olmayan sınıflardan alt-sınıflar türetilemez. Collections Çatısında ki sınıflardan alt-sınıflar türetilemez. Bazı durumlarda bu daha iyi olabilir, kalıtım(inheritance) yerine bileşim(composition) kullanmayı teşvik eder.

Statik fabrika metodlarının ikinci dezavantajı ise, diğer statik fabrika metodlarından kolaylıkla ayırt edilemez. Statik fabrika metodları için bazı ortak isimler şöyledir;

  • valueOf — Parametre ile aynı değere sahip bir örnek döner.
  • of — valueOf isminin alternatifi, EnumSet tarafından kullanılmıştır.
  • getInstance — Parametre tarafından belirtilen örneği döner ama aynı değeri olacağı söylenemez. Tekiz(singleton) sınıflarda, tek örneği döner.
  • newInstance — getInstance gibi ama her seferinde yeni bir örnek döner.
  • getType — Type ile örneği dönülecek nesnenin tipini belirler.
  • newType — newInstance gibi. Type ile örneği dönülecek nesnenin tipini belirler.

Sonuç olarak, statik fabrika metodları ve public yapılandırıcıların kendi kullanım alanları vardır. Anlatılanlara göre bunlara dikkat etmek gerekir. Genellikle, statik fabrika metodları daha tercih edilebilir olabilir.