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.
| Aggregation | Composition | |
|---|---|---|
| Bağ | Gevşek | Güçlü |
| Parçanın ömrü | Bağımsız | Bütüne bağlı |
| Parça nasıl gelir | Dışarıdan (referans) | İçeride yaratılır |
| Örnek | Takım–Oyuncu | Ev–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ılabilir1// 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}Ornek2
çalıştırılabilir1// 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) {}