Eğitim Portalı/Java/Aggregation ve Composition (HAS-A İlişkisi)
Java01-java/36-aggregation

Aggregation ve Composition (HAS-A İlişkisi)

Nesneler tek başına yaşamaz; birbirleriyle ilişkilidir. Kalıtım bölümünde IS-A ("bir tür ... olma": Köpek bir Hayvandır) ilişkisini gördük. Ama nesneler arası en yaygın ilişki aslında HAS-A ("sahip olma": Araba bir Mo…

Aggregation ve Composition (HAS-A İlişkisi)

Nesneler tek başına yaşamaz; birbirleriyle ilişkilidir. Kalıtım bölümünde IS-A ("bir tür ... olma": Köpek bir Hayvandır) ilişkisini gördük. Ama nesneler arası en yaygın ilişki aslında HAS-A ("sahip olma": Araba bir Motora sahiptir) ilişkisidir. Bu ilişkiyi modellemenin iki biçimi vardır — aggregation (gevşek) ve composition (güçlü) — ve doğru olanı seçmek temiz tasarımın anahtarıdır.

HAS-A: nesneleri birleştirmek

Bir sınıf, başka bir sınıf tipinde alanlar içerdiğinde HAS-A ilişkisi kurar:

class Araba {
    private Motor motor;      // Araba HAS-A Motor
    private List<Tekerlek> tekerlekler;
}

Bu, kodu küçük, tek-sorumluluklu parçalardan inşa etmeyi sağlar. HAS-A'nın iki türü, parçanın bütüne ne kadar bağımlı olduğuna göre ayrılır.

Aggregation (gevşek HAS-A)

Parça, bütünden bağımsız var olabilir. Bütün yok olsa bile parça yaşamaya devam eder; hatta aynı parça birden çok bütüne ait olabilir. Tipik olarak parça dışarıda yaratılır ve sınıfa referans olarak verilir:

Oyuncu o = new Oyuncu("Ada");   // oyuncu bağımsız var
takim.oyuncuEkle(o);            // takım ona referans tutar

Örnek 1 (./Ornek1.java): Bir Takim, Oyuncu'lara sahiptir ama oyuncular takımdan bağımsızdır — takım dağılsa bile oyuncular yaşar ve başka takıma geçebilir. UML'de içi boş elmas (<>--) ile gösterilir.

Composition (güçlü HAS-A)

Parça, bütüne sıkıca bağlıdır; ömrü bütünle başlar ve biter. Bütün olmadan parça anlamsızdır. Tipik olarak parça, sınıfın içinde yaratılır ve dışarı sızdırılmaz:

class Ev {
    private final List<Oda> odalar;
    Ev(int n) { /* odaları İÇERİDE yarat */ }   // odalar ev olmadan var olamaz
}

Örnek 2 (./Ornek2.java): Bir Ev, Oda'lardan oluşur; odalar ev oluşturulurken içeride yaratılır ve ev yok olursa anlamını yitirir. UML'de içi dolu elmas (*--) ile gösterilir.

AggregationComposition
BağGevşekGüçlü
Parçanın ömrüBağımsızBütüne bağlı
Parça nasıl gelirDışarıdan (referans)İçeride yaratılır
ÖrnekTakım–OyuncuEv–Oda, İnsan–Kalp
UMLİçi boş elmasİçi dolu elmas

"Kalıtım yerine kompozisyon" (composition over inheritance)

OOP'nin en değerli pratik kurallarından biri: mümkün olduğunca kalıtım yerine kompozisyonu tercih et. Neden?

  • Esneklik: Davranışı çalışma zamanında değiştirebilirsin (farklı bir parça enjekte ederek). Kalıtım derleme zamanında sabittir.
  • Gevşek bağlılık: Alt sınıf, üst sınıfın iç ayrıntısına sıkıca bağlıdır (kırılgan taban sınıf problemi). Kompozisyonda yalnızca arayüze bağlanırsın.
  • Çoklu davranış: Java tek kalıtıma izin verir; ama bir sınıf birçok parçaya sahip olabilir.

Örneğin "loglayan bir liste" istiyorsan, ArrayList'ten türetmek yerine (kalıtım), bir List'i içeren ve ona delege eden bir sınıf yazmak (kompozisyon) çok daha sağlamdır. Spring'in dependency injection'ı da özünde kompozisyondur: nesneler bağımlılıklarını (parçalarını) dışarıdan alır.

Özet

Nesneler arası HAS-A ilişkisini; gevşek aggregation (Takım–Oyuncu, parça bağımsız) ile güçlü composition'ı (Ev–Oda, parça bütüne bağlı) ve "kalıtım yerine kompozisyon" ilkesini öğrendik. Bu ilişkiler, esnek ve sürdürülebilir nesne tasarımının temelidir. Sırada, bir sınıfın içine başka sınıflar yerleştirmenin yolu: iç sınıflar (inner classes).

Kod Örnekleri(2)

Ornek1

çalıştırılabilir
Ornek1.java
1// Ornek1: Aggregation (gevşek HAS-A) — bir nesne, bağımsız yaşayabilen başka nesnelere SAHİPTİR.
2// Çalıştırma: java Ornek1.java
3import java.util.ArrayList;
4import java.util.List;
5
6public class Ornek1 {
7
8    public static void main(String[] args) {
9        // Oyuncular takımdan BAĞIMSIZ var olabilir (önce oluşturulur).
10        Oyuncu o1 = new Oyuncu("Ada");
11        Oyuncu o2 = new Oyuncu("Burak");
12
13        Takim takim = new Takim("Yıldızlar");
14        takim.oyuncuEkle(o1);   // takım, var olan oyuncuya REFERANS tutar (aggregation)
15        takim.oyuncuEkle(o2);
16        takim.kadroYaz();
17
18        // Takım dağılsa bile oyuncular yaşamaya devam eder (başka takıma geçebilir).
19        Takim digerTakim = new Takim("Şimşekler");
20        digerTakim.oyuncuEkle(o1); // aynı oyuncu başka takımda da olabilir
21        System.out.println("\n" + o1.ad() + " iki takımda da var: aggregation gevşek bir bağdır.");
22
23        System.out.println("""
24
25                --- Aggregation (gevşek HAS-A) ---
26                "Sahip olma" ilişkisi ama bağ GEVŞEK: parça, bütünden bağımsız var olabilir.
27                Takım <>-- Oyuncu: takım oyunculara sahiptir ama oyuncular takım olmadan da yaşar.
28                Tipik: nesne dışarıdan verilir (constructor/metot ile referans alınır).""");
29    }
30}
31
32record Oyuncu(String ad) {}
33
34class Takim {
35    private final String ad;
36    private final List<Oyuncu> oyuncular = new ArrayList<>();
37    Takim(String ad) { this.ad = ad; }
38
39    // Var olan bir oyuncuya referans tutar (oyuncuyu burada YARATMAZ).
40    void oyuncuEkle(Oyuncu o) { oyuncular.add(o); }
41    void kadroYaz() {
42        System.out.println(ad + " kadrosu: " + oyuncular.stream().map(Oyuncu::ad).toList());
43    }
44}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.

Ornek2

çalıştırılabilir
Ornek2.java
1// Ornek2: Composition (güçlü HAS-A) — parça, bütün olmadan var olamaz; ömrü bütüne bağlıdır.
2// Çalıştırma: java Ornek2.java
3import java.util.List;
4
5public class Ornek2 {
6
7    public static void main(String[] args) {
8        // Ev oluşturulduğunda odaları da ONUNLA BİRLİKTE oluşur (içeride yaratılır).
9        Ev ev = new Ev(3);
10        ev.bilgiYaz();
11        System.out.println("Toplam alan: " + ev.toplamAlan() + " m²");
12        // Odalar evin İÇİNDE yaratıldı; ev yok olursa odalar da anlamını yitirir (composition).
13
14        System.out.println("""
15
16                --- Composition (güçlü HAS-A) ---
17                Parça, bütüne SIKICA bağlıdır ve ömrü onunla başlar/biter.
18                Ev *-- Oda: odalar ev oluşturulurken İÇERİDE yaratılır; ev olmadan oda olmaz.
19                Tipik: parça nesneler sınıfın İÇİNDE 'new' ile yaratılır ve dışarı sızdırılmaz.""");
20    }
21}
22
23class Ev {
24    private final List<Oda> odalar;
25
26    Ev(int odaSayisi) {
27        // Odaları DIŞARIDAN almıyoruz; evin parçası olarak burada YARATIYORUZ.
28        var liste = new java.util.ArrayList<Oda>();
29        for (int i = 1; i <= odaSayisi; i++) liste.add(new Oda("Oda-" + i, 12 + i));
30        this.odalar = List.copyOf(liste);
31    }
32
33    void bilgiYaz() {
34        System.out.println("Ev (" + odalar.size() + " oda):");
35        odalar.forEach(o -> System.out.println("  " + o.ad() + " - " + o.metrekare() + " m²"));
36    }
37    int toplamAlan() { return odalar.stream().mapToInt(Oda::metrekare).sum(); }
38}
39
40// Oda yalnızca Ev'in içinde anlamlı (bu örnekte dışarı sızdırılmıyor).
41record Oda(String ad, int metrekare) {}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.