Madde 51’de anlattığımız gibi parametre türleri olarak sınıflar yerine arayüzleri kullanmalıyız. Bunu daha da genelleyecek olursak, nesneleri işaret etmek için sınıflar yerine arayüzleri kullanmalıyız. Eğer uygun bir arayüz türü mevcutsa, parametreler, dönüş değerleri, değişkenler ve alanlar arayüz türü ile tanımlanmalıdır. Bunun tek istisnası yapıcı metot içerisinde nesne yarattığımız zamandır. Bunu örneklemek için aşağıdaki satıra bakalım. LinkedHashSet
, Set
arayüzünü uygulayan bir sınıftır.
// Doğru kullanım, referans arayüz türü ile tanımlanmış
Set<Son> sonSet = new LinkedHashSet<>();
Aşağıdaki da yanlış kullanıma bir örnektir:
// Yanlış kullanım - sınıf türü kullanılmış
LinkedHashSet<Son> sonSet = new LinkedHashSet<>();
Eğer referans türü olarak arayüzleri kullanmayı alışkanlık haline getirirseniz, programlarınız çok daha esnek olacaktır. Eğer sonradan aynı arayüzü uygulayan başka bir sınıf kullanmak isterseniz, tek yapmanız gereken sınıf adını ve yapıcı metot çağrısını değiştirmek olacaktır. Örneğin, yukarıdaki birinci kod örneğini aşağıdaki gibi değiştirmek mümkündür:
Set<Son> sonSet = new HashSet<>();
Programın geri kalanı sorunsuz biçimde çalışmaya devam edecektir. Diğer kodlar kullanılan önceki sınıftan haberdar olmadıkları için yapılan değişiklikten de etkilenmezler.
Ancak burada dikkat etmemiz gereken bir husus var. Eğer kullanılan ilk sınıfta arayüzde tanımlanmamış bir özellik varsa ve kodun geri kalanı da bu özelliğe bağımlıysa, kullanmak istediğimiz yeni sınıfın da aynı özelliğe sahip olması gerekir. Örneğin, eğer kodun geri kalanı LinkedHashSet
ile gelen sıralama özelliğini kullanıyorsa bunu HashSet
ile değiştirmek çalışmayacaktır çünkü HashSet
elemanlar arasında herhangi bir sıralama yapacağını garanti etmez.
Peki o zaman neden gerçekleştirim türünü değiştirmek isteyesiniz? Bunun sebebi ikinci gerçekleştirimin ilkine göre daha performanslı çalışması olabilir veya birincide eksik olan bazı özelliklerin eklenmesi olabilir. Örneğin, HashMap
türünde bir değişkeniniz varsa bunu EnumMap
ile değiştirmek hem hızı artıracaktır hem de anahtar değerler enum türünde olmak zorunda olduğu için enumların bazı avantajlarından faydalanmak da mümkün olur. Eğer anahtar değerler enum olarak tanımlanamıyorsa, EnumMap
yerine LinkedHashMap
kullanmak isteyebilirsiniz. Bu durumda daha tahmin edilebilir bir iterasyon (yineleme) özelliği kazanmış olursunuz.
Eğer kullanabileceğiniz uygun bir arayüz yoksa, nesneleri yaratıldıkları sınıf türünde referanslarla tanımlamak gayet normaldir. Örneğin, String
ve BigInteger
gibi türleri düşünün. Bu tür sınıflar birden fazla gerçekleştirim yazılabileceği düşünerek yazılmamıştır. O yüzden final
olarak tanımlanmış ve arayüz kullanılmamıştır. O yüzden bu tür değer sınıflarını (value class) sınıf isimleriyle kullanmakta bir sorun yoktur.
Buna ikinci bir örnek de sınıf-tabanlı bazı uygulama çatılarının (framework) içerisinde gelen ve temel olarak arayüz yerine sınıfları kullanan türlerdir. Eğer bu şekilde bir sınıftan yaratılmış bir nesneniz varsa, bunları temsil etmek için genellikle abstract
olan ata sınıf türlerini kullanmalıyız. OutputStream
gibi birçok java.io
sınıfı bu kategoridedir.
Son olarak, sınıf bir arayüzü uygulasa bile bazı durumlarda kendi içinde başka metotlar da tanımlamış olabilir. Örneğin Queue
arayüzünde olmamasına rağmen, PriorityQueue
sınıfı içerisinde bir comparator
metodu bulunmaktadır. Eğer istemci kodu bu ekstra metodu kullanıyorsa, arayüz yerine sınıf türünden bir referans tanımlanmasında bir sakınca yoktur.
Bu üç örnek referans türleri tanımlarken sınıf kullanmanın hangi durumlarda normal olduğunu göstermek için seçilmiştir ancak bunun dışında başka durumlar da olabileceği açıktır. En nihayetinde, bir sınıfı temsil etmek için uygun bir arayüz olup olmadığı çabucak anlaşılabilir. Eğer böyle bir arayüz varsa ve siz de bunu referans türü olarak kullanırsanız, programlarının daha esnek ve zarif olacaktır. Eğer uygun arayüz yoksa, size ihtiyacınız olan bütün özellikleri sağlayan en yukarıdaki ata sınıfı kullanın.