Berk Akademi
Birebir ders başvurusu Ücretsiz ön görüşme Ana Sayfa

Java Composition ve Inheritance Farkı Ne Zaman Önemlidir?

java-composition-inheritance-farki-ne-zaman-onemlidir
Bu yazıda neler var?
  1. Composition ve inheritance nedir? Önce is-a ve has-a ilişkisini ayırın
  2. Dört ölçütle karar verin: is-a, sözleşme, davranış ve değişim etkisi
  3. Composition alan üzerinden esneklik, inheritance hiyerarşi üzerinden paylaşım sağlar
  4. Interface neden inheritance ile aynı şey değildir?
  5. ReportService örneği: Aynı ihtiyacı composition ve inheritance ile modellemek
  6. Örnekten sonra kullanabileceğiniz composition mı inheritance mı kontrol listesi
  7. Sık Sorulan Sorular

Java composition ve inheritance farkı, yalnızca kod tekrarını azaltma isteğine göre belirlenmez. Seçimde sınıflar arasındaki ilişkinin anlamı, ortak sözleşmenin korunup korunmadığı, davranışın kime ait olduğu ve yapılacak değişikliklerin hangi kodları etkileyeceği birlikte değerlendirilmelidir.

Composition, değiştirilebilir bir bağımlılığı başka bir nesne üzerinden kullanmayı; inheritance ise gerçek ve güvenilir bir alt tür ilişkisini ifade eder. Bu nedenle “Bir sınıf başka bir sınıfın özel bir türü değilse, kalıtım yerine bileşim seçimini önce değerlendir.” yaklaşımı, başlangıç için pratik bir karar kuralıdır.

Composition ve inheritance nedir? Önce is-a ve has-a ilişkisini ayırın

Composition, bir sınıfın başka bir nesneyi alan olarak tutması ve onunla iş birliği yapmasıdır. Bu ilişki has-a olarak ifade edilir. Örneğin bir rapor servisi, loglama işlemini kendi içinde gerçekleştirmek yerine bir Logger nesnesine sahip olabilir: ReportService has-a Logger.

Inheritance ise bir temel sınıf ile onu genişleten alt sınıf arasındaki ilişkidir. Burada alt sınıf, temel sınıfın anlamlı ve özel bir türü olmalıdır. Örneğin dosyaya log yazan bir sınıf, genel bir loglayıcının özel biçimi olarak modellenebilir: FileLogger is-a Logger.

class Logger {
    void log(String message) {
        System.out.println(message);
    }
}

class FileLogger extends Logger {
    @Override
    void log(String message) {
        System.out.println("Dosyaya yazıldı: " + message);
    }
}

class ReportService {
    private final Logger logger;

    ReportService(Logger logger) {
        this.logger = logger;
    }

    void createReport() {
        logger.log("Rapor oluşturuldu.");
    }
}

Bu örnekte FileLogger, Logger sınıfından kalıtım aldığı için bir Logger türüdür. ReportService ise bir Logger değildir; yalnızca loglama davranışını kullanmak için bir Logger nesnesi tutar. new ReportService(new FileLogger()) çağrısı yapıldığında beklenen çıktı Dosyaya yazıldı: Rapor oluşturuldu. olur.

Dört ölçütle karar verin: is-a, sözleşme, davranış ve değişim etkisi

Dört ölçütle karar verin: is-a, sözleşme, davranış ve değişim etkisi

İki seçenek arasında karar verirken aşağıdaki dört ölçütü sırayla kontrol edebilirsin. Kod tekrarının bulunması, tek başına kalıtım kullanmak için yeterli gerekçe değildir.

Ölçüt Composition için işaret Inheritance için işaret Kontrol sorusu
İlişkinin anlamı Bir nesne, başka bir nesneyi kullanıyor veya içeriyor. Alt sınıf, temel sınıfın özel bir türü olarak anlamlı. “Bu nesne gerçekten o türden biri mi?”
Ortak sözleşme Bağımlılık farklı nesnelerle değiştirilebilir. Alt sınıf, temel sınıfın beklediği davranışı güvenilir biçimde koruyor. Temel sınıfı bekleyen kod alt sınıfla da doğru çalışır mı?
Davranışın sahipliği Davranış ayrı bir bileşenin sorumluluğunda. Paylaşılan davranış hiyerarşinin ortak ve anlamlı parçası. Bu davranış ortak türün mü, yoksa kullanılan bir hizmetin mi?
Değişim etkisi Bağımlılık daha sınırlı etkiyle değiştirilebilir. Temel davranışın değişmesi alt sınıfları da bilinçli biçimde etkiler. Temel sınıftaki değişiklik kaç sınıfı ve çağıranı etkiler?

Değiştirilebilir bir bağımlılığa ihtiyaç duyuyorsan composition genellikle daha uygun bir başlangıç noktasıdır. Buna karşılık gerçek, güvenilir ve ortak bir alt tür ilişkisi varsa inheritance anlamlı olabilir. Son kararı “aynı metotlar var mı?” sorusuyla değil, dört ölçütün tamamını değerlendirerek vermelisin.

Composition alan üzerinden esneklik, inheritance hiyerarşi üzerinden paylaşım sağlar

Composition, bir sınıfın ihtiyaç duyduğu davranışı başka bir nesneyi alanında tutarak kullanmasıdır. Bu alan çoğunlukla private final olarak tanımlanır ve nesne constructor üzerinden alınır. Böylece alanın referansı sınıf oluşturulduktan sonra yeniden atanmaz; davranışın hangi uygulama tarafından sağlanacağı ise sınıfın dışından belirlenebilir.

interface Formatter {
    String format(String text);
}

class MessagePrinter {
    private final Formatter formatter;

    MessagePrinter(Formatter formatter) {
        this.formatter = formatter;
    }

    void print(String text) {
        System.out.println(formatter.format(text));
    }
}

class UpperCaseFormatter implements Formatter {
    @Override
    public String format(String text) {
        return text.toUpperCase();
    }
}

MessagePrinter doğrudan UpperCaseFormatter sınıfına bağlı değildir; yalnızca format davranışına ihtiyaç duyar. Bu nedenle aynı sınıfa farklı bir biçimlendirici verilebilir. new MessagePrinter(new UpperCaseFormatter()).print("merhaba") çağrısı ekrana MERHABA yazdırır. Değişiklik yapmak için yeni bir alt sınıf hiyerarşisi kurmak yerine, kullanılan nesneyi değiştirmek yeterlidir.

Inheritance ise extends ile bir temel sınıf ve alt sınıf arasında hiyerarşik ilişki kurar. Alt sınıf, temel sınıfta erişilebilir olan davranışları devralabilir ve gerektiğinde @Override ile bu davranışları kendi ihtiyacına göre ezebilir. Bu yaklaşım, ortak davranışın aynı hiyerarşi içindeki sınıflar arasında paylaşılmasını kolaylaştırır; ancak alt sınıfı temel sınıfın yapısına ve gelecekteki hiyerarşi değişikliklerine daha yakından bağlar.

Bu nedenle sınıfın başka bir sınıfı kullanması ile onun bir türü olması birbirine karıştırılmamalıdır. Java sınıf tasarımını örnekler üzerinden yapılandırılmış biçimde çalışmak isteyenler, birebir Java özel ders programını inceleyebilir.

Bir sınıf başka bir sınıfın özel bir türü değilse, kalıtım yerine bileşim seçimini önce değerlendir.

Interface neden inheritance ile aynı şey değildir?

Interface neden inheritance ile aynı şey değildir?

Interface, bir nesnenin sunması beklenen davranışları tanımlayan bir sözleşmedir. Sınıf inheritance’ı ise temel sınıf ile alt sınıf arasında hiyerarşik ilişki ve uygulanabilir davranış paylaşımı kurar. Bir sınıfın interface uygulaması, o sınıfın interface’te belirtilen davranışı sağlayabildiğini gösterir; bu, interface’in içindeki başka bir sınıfın kodunu devraldığı anlamına gelmez. Java’da sınıflar interface’i implements ile uygular, sınıflar ise temel sınıflarını extends ile belirtir.

interface Logger {
    void log(String message);
}

class FileLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("[dosya günlüğü] " + message);
    }
}

class ReportService {
    private final Logger logger;

    ReportService(Logger logger) {
        this.logger = logger;
    }

    void create() {
        logger.log("Rapor oluşturuldu");
    }
}

Burada ReportService, FileLogger sınıfının iç işleyişini bilmez; yalnızca Logger sözleşmesine güvenir. Başka bir sınıf da Logger interface’ini uyguladığında, ReportService değişmeden bu yeni nesne kullanılabilir. Interface tek başına her durumda kod paylaşımı sağlamaz; asıl katkısı, tüketen sınıfı belirli bir somut uygulamadan ayırarak değiştirilebilirliği artırmasıdır.

ReportService örneği: Aynı ihtiyacı composition ve inheritance ile modellemek

Aynı “rapor oluşturulunca log yaz” ihtiyacını iki farklı tasarımla inceleyelim. Aşağıdaki kod blokları birbirinden bağımsız alternatiflerdir; aynı sınıf adlarını kullandıkları için birlikte değil, ayrı ayrı değerlendirilmelidir.

Composition ile çözüm

interface Logger {
    void log(String message);
}

class FileLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("FILE: " + message);
    }
}

class ReportService {
    private final Logger logger;

    public ReportService(Logger logger) {
        this.logger = logger;
    }

    public void createReport() {
        logger.log("Rapor oluşturuldu");
    }
}

class CompositionDemo {
    public static void main(String[] args) {
        new ReportService(new FileLogger()).createReport();
    }
}

new ReportService(new FileLogger()).createReport() çağrısı FILE: Rapor oluşturuldu benzeri tek bir çıktı üretir. ReportService, FileLogger değildir; yalnızca Logger sözleşmesine uyan bir nesneyi kullanır. Bu nedenle başka bir logger uygulaması constructor üzerinden verilebilir ve servis kodu değişmeden davranış değiştirilebilir. Java’da interface bir tür olarak kullanılabildiği için bu bağımlılık farklı uygulamalarla değiştirilebilir.

Inheritance ile çözüm

class Logger {
    public void log(String message) {
        System.out.println("LOG: " + message);
    }
}

class FileLogger extends Logger {
    @Override
    public void log(String message) {
        System.out.println("FILE: " + message);
    }
}

class ReportService extends FileLogger {
    public void createReport() {
        log("Rapor oluşturuldu");
    }
}

class InheritanceDemo {
    public static void main(String[] args) {
        new ReportService().createReport();
    }
}

Bu sürüm de çalışır ve aynı çıktıyı verir. Ancak ReportService extends FileLogger ifadesi, rapor servisini bir FileLogger türü olarak konumlandırır. Alan bilgisi açısından şu soruyu sormak gerekir: Bir rapor servisi gerçekten dosya logger’ının özel bir türü müdür? Değilse, kod tekrarı azalsa bile hiyerarşi yanlış bir anlam taşır. Kalıtımda alt sınıfın üst sınıf davranışını ezebilmesi güçlü bir özelliktir; bu özellik güvenilir bir “is-a” ilişkisi olduğunda kullanılmalıdır.

Örnekten sonra kullanabileceğiniz composition mı inheritance mı kontrol listesi

Kararı yalnızca hangi seçeneğin daha kısa kod ürettiğine bakarak verme. Aşağıdaki sorular, ilişkinin yapısal mı yoksa yalnızca kullanım amaçlı mı olduğunu anlamana yardımcı olur:

  1. Bir sınıf gerçekten diğerinin özel bir türü mü?
  2. Alt tür, temel türün yerine geçtiğinde sözleşme ve beklentiler korunuyor mu?
  3. Paylaşılmak istenen davranış sınıf kimliğinin parçası mı, yoksa dışarıdan sağlanabilecek bir hizmet mi?
  4. Temel sınıf değiştiğinde oluşacak bağımlılık zinciri kabul edilebilir mi?
  5. Davranışı constructor üzerinden değiştirme veya test için farklı bir uygulama verme ihtiyacı var mı?
  6. Sınıfın adı ve sorumluluğu, kurulan ilişkiyi doğal biçimde açıklıyor mu?

Gerçek ve güvenilir bir alt tür ilişkisi varsa inheritance değerlendirilebilir. İlişki daha çok “kullanır” biçimindeyse composition seçimini önce incelemek genellikle daha açıklayıcı bir tasarım sağlar.

Bir sınıf başka bir sınıfın özel bir türü değilse, kalıtım yerine bileşim seçimini önce değerlendir.

Kendi Java sınıf tasarımında bu soruları birlikte uygulamak ve yapına özel geri bildirim almak istersen randevulu birebir Java dersleri hakkında bilgi alabilirsin.

Sık Sorulan Sorular

Bir sınıf aynı anda composition kullanıp inheritance alabilir mi?

Evet. Bir sınıf bir üst sınıftan kalıtım alırken aynı zamanda alanlarında başka nesneleri tutabilir. Örneğin bir servis temel bir sınıftan ortak davranış alırken, loglama işini ayrıca bir Logger nesnesine bırakabilir.

Interface kullanmak neden sınıf inheritance’ı ile aynı kabul edilmez?

Interface, öncelikle bir sözleşme ve referans türü sağlar. Sınıf inheritance’ında ise alt sınıf bir üst sınıftan davranış ve alanlar devralır. Ayrıca bir Java sınıfının doğrudan bir üst sınıfı bulunurken birden fazla interface uygulaması mümkündür.

Kod tekrarını azaltmak için inheritance seçmek hangi durumlarda risklidir?

İki sınıf arasında gerçek bir “is-a” ilişkisi yoksa risk artar. Alt sınıf gereksiz metotlara ve duruma bağımlı olabilir; temel sınıftaki değişiklikler de beklenmedik biçimde alt sınıfları etkileyebilir.

ReportService içindeki Logger bağımlılığı nasıl değiştirilebilir veya test edilebilir?

Composition örneğinde bağımlılık constructor üzerinden verildiği için FileLogger yerine aynı interface’i uygulayan başka bir logger kullanılabilir. Test sırasında da gerçek çıktı üretmeyen, çağrıyı kaydeden basit bir test logger’ı sağlanabilir.

Doğru seçim, yalnızca kod tekrarını azaltan değil, sınıflar arasındaki gerçek ilişkiyi en açık biçimde anlatan seçimdir.

Bu içerik aradığın cevabı verdi mi?
Yanıtın, hangi yazıları geliştirmemiz gerektiğini anlamamıza yardımcı olur.
Bu içeriğin üretilmesinde yapay zeka araçlarından destek alınmıştır.

Bu konudan sonra ne okuyabilirsin?

Tüm yazılar

İlgili Eğitimler

Berk Keskin, yazılım geliştirici ve eğitmen
Yazar

Berk Keskin Kimdir?

Yazılıma 12 yaşında başladı; İzmir Ekonomi Üniversitesi'ni bölüm birincisi ve yüksek şeref öğrencisi olarak tamamladı. Bugün yalnızca eğitim vermekle kalmıyor, sektörde aktif olarak yazılım projeleri geliştiriyor ve gerçek dünya deneyimini birebir derslerine taşıyor. Ezberden uzak, mühendislik zihniyetini merkeze alan sürdürülebilir öğrenme sistemleri tasarlayarak sorgulayan, üreten ve problem çözebilen yeni nesil yazılımcılar yetiştiriyor.

Sektörel Deneyim & Projeler

  • Ticarify Entegrasyon Yazılım logosu CEO Ticarify Entegrasyon YazılımPazaryerleri ve e-ticaret sitelerine otomatik e-fatura kesimi, sipariş ve kargo takibi hizmetleri sunan e-Dönüşüm platformunun API mimarisini ve yazılım ekibini yönetmektedir.
  • Benim Düğünüm logosu CEO Benim DüğünümDijital etkinlik ve anı paylaşım platformu.
  • Siberdizayn logosu Yazılım Ekibi Lideri SiberdizaynYüksek anlık oyuncu trafiğine sahip oyun kontrol panelleri ve sunucu altyapıları geliştiren yazılım ekibine liderlik etmektedir.
  • MEDYOGRAFYA 360° Dijital Çözümler logosu Dijital Strateji Lideri MEDYOGRAFYA 360° Dijital ÇözümlerŞirketlerin dijital çözümlerde uzun vadede nasıl ilerlemesi gerektiği ve dijital dönüşüm süreçlerinin yönetilmesine destek olmaktadır.
  • İzmir Ekonomi Üniversitesi logosu Danışma Kurulu Üyesi İzmir Ekonomi ÜniversitesiMezun olduğu üniversitesinde, Bilgisayar Programcılığı bölümünün akademik müfredatını güncel sektör ihtiyaçlarına göre şekillendirmek adına Danışma Kurulu'nda görev almaktadır.
WhatsApp Hemen Ara