taban sınıf olarak tasarlanmamış sınıflardan türetme yapmaktan kaçının

Kalıtımda taban sınıf olacak şekilde tasarlanmamış bir sınıftan kalıtım yoluyla yeni bir sınıf oluşturmak hemen her zaman bir tasarım hatasıdır. Somut sınıflardan kalıtım yapmaktan kesinlikle kaçınmalıyız. Çocuk sahibi olmaya hazır olmayan birini buna zorlamamalısınız, değil mi?
Bazı programcılar, nesne yönelimli programlamanın ana gücünün kalıtım olduğunu düşündüklerinden hemen her fırsatta kalıtımdan faydalanmaya çalışıyorlar ve kalıtım amaçlı tasarlanmamış ayrık (standalone) sınıflardan da türetme yoluna gidiyorlar. Bu tür girişimler, tipik olarak derleyicinin uyarı mekanizmasını devre dışı bırakacak birçok soruna yol açabiliyor. Başlangıç seviyesindeki programcılar, bir değer sınıfının (value class) var olan kodlarından faydalanmak, ona daha fazla işlevsellik katmak için yeni üye işlevler ekleyerek kalıtım yoluna başvuruyorlar. Oysa türetme yapmak yerine, gereken ek işlevselliği global işlevler ile sağlamak hemen her zaman çok daha iyi ve çok daha güvenli.

Bir banka hesabını temsil eden, kalıtım amaçlı tasarlanmamış Account isimli bir sınıfımız olsun. Account sınıfının public arayüzünde hesabın ortalama günlük bakiyesini hesaplayan bir üye işlev yok ve biz böyle bir işleve gereksinim duyuyoruz. Bu amaçla, Account sınıfından kalıtım yoluyla AccountDer isimli bir sınıf oluşturduğumuzu düşünelim. Kalıtım yoluyla elde ettiğimiz sınıf kendi arayüzüne getAverageBalance isimli bir işlevi dahil ediyoruz.  Bu yalnızca bir örnek. AccountDer sınıfına katmak istediğimiz daha fazla işlevsellik olabilir.

Peki diğer olasılık ne? Bu işi yapacak global bir işlev oluşturmak:

Bu işlevin isminin aranıp bulunmasında bir sorun olmaması için, işlev bildiriminin sınıfın tanımının yapıldığı isim alanına koyulması gerekir.

Kalıtım yerine global işlev ya da işlevler tanımlamak neden kalıtımdan çok daha iyi, şimdi bunu tartışalım:

Global bir işlev var olan kod tabanına (codebase) kolaylıkla uyum sağlayacak iken, kalıtımla üretilen sınıf için var olan kodlardaki türleri ve bazı işlevlerin imzalarını değiştirmemiz gerekebilir. Bazı işlevlerin imzalarında bulunan Account tür isimlerini AccountDer olarak değiştirmek zorunda kaldığımız yerler olacak.

Müşteri kodlar için sunulan arayüzde Account isteyen işlevler AccountDer sınıfının sağladığı ilave işlevsellikten ya faydalanamayacaklar ya da bu işlevsellikten faydalanmaları için bir kopyalama yapılması (ilave maliyet) ve buna bağlı olarak tür dönüştürme (type conversion)  işlemi yapılması gerekecek. Bu da kodlama hatası oluşturma riskini yükseltecek.

AccountDer sınıfı Account sınıfının içsel verilerine erişemeyecek. Account sınıfı kalıtım için tasarlanmadığından bu sınıfın bir protected bölümü ve o bölümde tutulan veri öğeleri yok.
Eğer AccountDer sınıfı Account sınıfıyla aynı isimli işlev ya da işlevler tanımlarsa, bu işlev yüklemesi (function overloading) olmayacak. AccountDer sınıfına eklenecek Account sınıfındaki bulunan isimler Account sınıfındaki isimleri gizleyecekler.

Kalıtım için tasarlanmamış olan Account sınıfının sonlandırıcı işlevi sanal değil. Bu durumda dinamik bir AccountDer nesnesini Account sınıfı tarafından bir gösterici ya da akıllı gösterici tarafından kontrol edildiğinde ve delete işleminin Account göstericisi ile yapılması durumunda (dynamic deletion) tanımsız davranış oluşacak. AccountDer sınfıının sonlandırıcı işlevi çağrılmayacağı gibi bellek sızıntısı, heap alanının bozulması gibi sorunlar da oluşabilecek.

Peki ya türemiş sınıf taban sınıfın durum verilerine ek olarak kendisi de bir durum verisi eklemek istiyorsa yani oluşturcağımız yeni sınıfa yeni veri öğeleri eklemek istiyorsak ne yapacağız? En önemli tasarım ilkelerinden birine uyacağız. Her zaman olduğu gibi sahip olma ilişkisini (composition) kalıtıma tercih edeceğiz: Oluşturacağımız yeni sınıf bu durumda Account sınıfı türünden bir veri öğesine ve istediği diğer veri öğelerine sahip olacak:

Artık Account sınıfı göstericisi ile SAccount türünden bir nesneyi delete etmek gibi bir sorunumuz yok.
SAccount sınıfı Account sınıfının herhangi bir işlevinin gerçekleştirimini (implementation) değiştirebilir.
SAccount sınıfı Account sınıfının arayüzündeki her işlevi kendi arayüzüne katmak zorunda değil.
SAccount sınıfı Account sınıfının arayüzünde olmayan, eklemek istediği işlevselliği kendi arayüzüne ekleyebilir.

Ama bu durumda Account sınıfının arayüzü SAccount sınıfının arayüzüne katılmaz değil mi? Katılmasın, biz de Account sınıfının arayüzündeki işlevleri SAccount sınıfının arayüzüne kendimiz ekleyebilir ve bu işlevlerin öğe olarak kullanılan Account nesnesinin ilgili üye işlevini çağırmasını sağlayabiliriz. Bu tür işlevlere İngilizcede “passthrough function” deniyor.  Evet bu iş biraz zahmetli. Ama daha iyi tasarlanmış, kodlama hatalarına karşı daha güvenilir kod için birazcık zahmete değer, değil mi?

 

Share

Necati Ergin

C ve Sistem Programcıları Derneğinde eğitmen olarak çalışıyor.

Bunlar da ilginizi çekebilir

Kod Eklemek İçin Okuyun