İçeriğe geç

Sınıf, Nesne ve Kapsülleme — Kuralı Kim Koruyor?

Başlangıç 8 dk Çok sık karşılaşılır

30 saniyede özet

Kapsülleme alanları private yapmak değil, nesnenin kurallarını yalnızca kendi metotlarıyla değiştirilebilir kılmaktır. Kural genelde iki arka kapıdan kaçar: iç listeyi veren getter ve dışarıdaki listeyi saklayan constructor.

Siparişin toplamı 350 TL diyor, satırlar 390 TL ediyor. Kimse total alanına dokunmadı; alan zaten private. Satırlar ise başka bir kapıdan değişti.

  1. Bayt: Siparişin toplamı 350 diyor, satırlar 390 ediyor!

  2. Sen: Ama total alanı private. Kimse ona dokunamaz ki.

  3. Bayt: Total'e dokunan yok. Peki satırlara başka bir kapıdan giren var mı?

  4. Bayt: Hadi bakalım: bu nesnenin kuralını kim koruyor?

Nesne kendi kuralını korur

Bir nesne yalnızca veri tutmaz, o verinin uyması gereken kuralı da taşır. Sipariş (Order) için kural basit: toplam, her zaman satırların toplamına eşittir. Hiç bozulmaması gereken bu tür kurallara değişmez kuralNesne var olduğu sürece doğru kalması gereken şey. Kapsüllemenin ölçütü kaç alanın private olduğu değil, hangi kuralın dışarıdan bozulamadığıdır.Bir setter, koruduğu kural varsa onu atlanabilir kılar. Kuralı metodun içine koymak (withdraw gibi) nesneyi geçersiz duruma düşürmeyi imkânsız hâle getirir.Sözlükte gör → denir.

kapsüllemeBir nesnenin iç durumunu yalnızca kendi metotlarından değiştirilebilir kılmak. Amaç alanları gizlemek değil, nesnenin kuralını (invariant) korumaktır.Sözlükte gör →, veriyi yalnızca kuralı bilen metotların değiştirebilmesidir. addLine() satırı eklerken toplamı da günceller; iki iş hep birlikte yapıldığı için kural bozulamaz.

Her alana bir setter açmak kapsülleme değildir. setTotal() varsa herkes toplamı istediği sayıya çekebilir ve kuralı koruyan kimse kalmaz.

Kafam karıştı, daha basit anlat

Kasiyer düşün: sepete ürün eklerken toplamı da o günceller. Müşteri toplamı kendi eliyle değiştiremez. Kapsülleme, veriyi yalnızca kuralı bilenin değiştirmesidir.

Hızlı kontrolBaşlangıç

Kapsüllemenin asıl amacı nedir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Bir sınıfın bütün alanları private ama her biri için public getter ve setter var. Bu sınıf kapsüllenmiş midir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

final ve private yetmez

private final List<Integer> lines alanı ve getLines() { return lines; } var. Dışarıdaki kod order.getLines().add(40) yazarsa ne olur? Cevabı göster

Ekleme çalışır, toplam güncellenmez. final yalnızca referansı sabitler; getter iç listenin kendisini dışarı verdi.

Ön kapı kuralı bilir, arka kapı bilmez.
Adım adım oku
  1. addLine ön kapıdır: satırı eklerken toplamı da günceller. Kural tamam.
  2. getLines ise iç listenin kendisini dışarı verir: bir arka kapı açılır.
  3. Dış kod bu listeye 40 ekler. Satırlar 390 olur, toplam hâlâ 350: kural bozuldu.
  4. Çözüm: dışarıya listenin kopyasını ya da değiştirilemez bir görünümünü ver. Dış kod ancak kendi kopyasını değiştirebilir.

Kaçak yollardan ikincisi constructor’dır. this.lines = lines yazınca Order, dışarıdan verilen listenin kendisini saklar. Listeyi veren kişi onu sonra değiştirirse, Order’ın içi de habersizce değişir.

İki kapıyı da kapat. İçeri alırken listenin bir kopyasını al (buna defensive copyDışarıdan alınan ya da dışarıya verilen değiştirilebilir bir nesnenin kopyasını kullanmak; böylece başkasının referansı iç durumu değiştiremez.Sözlükte gör → denir), dışarı verirken de değiştirilemeyen bir liste ver.

Aşağıdaki örnek bir bankanın vadesiz hesabı. Kural iki parçalı: bakiye her zaman hareketlerin toplamına eşittir ve ek hesap limitinin altına inmez. İki alanı da değiştiren tek bir kapı var.

Derinleş · Vadesiz hesap: kuralı tek kapıdan korumak 4 dosya · ~128 satır · ilk okumada atlayabilirsin
Proje dosyaları

src/main/java/com/bank/account/ Account.java Hesap: para yalnızca deposit ve withdraw ile hareket eder. İkisi de tek bir özel metottan geçer; bakiye ve hareketler hep birlikte değişir.

src/main/java/com/bank/account/Account.java
public final class Account {
private final String iban;
private final Money overdraftLimit;
private final List<Movement> movements;
private Money balance;
// Rebuilds an account from its stored history: copy first, then check the copy.
public Account(String iban, Money overdraftLimit, List<Movement> history) {
this.iban = Objects.requireNonNull(iban, "iban");
this.overdraftLimit = Objects.requireNonNull(overdraftLimit, "overdraftLimit");
this.movements = new ArrayList<>(history); // defensive copy: the caller's list stays theirs
this.balance = new Money(BigDecimal.ZERO, overdraftLimit.currency());
for (Movement movement : movements) {
balance = balance.plus(movement.amount());
}
requireWithinLimit(balance);
}
public void deposit(Money amount, String description, Instant bookedAt) {
requirePositive(amount);
book(new Movement(bookedAt, amount, description));
}
public void withdraw(Money amount, String description, Instant bookedAt) {
requirePositive(amount);
requireWithinLimit(balance.minus(amount)); // check before any state changes
book(new Movement(bookedAt, amount.negate(), description));
}
// Money is immutable, so handing it out opens no back door.
public Money balance() {
return balance;
}
// A read-only window: callers see new movements, only this class can add them.
public List<Movement> movements() {
return Collections.unmodifiableList(movements);
}
// The single door: the movement and the balance always change together.
private void book(Movement movement) {
movements.add(movement);
balance = balance.plus(movement.amount());
}
private void requireWithinLimit(Money candidate) {
if (candidate.isLessThan(overdraftLimit.negate())) {
throw new IllegalStateException("overdraft limit exceeded on " + iban);
}
}
private static void requirePositive(Money amount) {
if (amount.amount().signum() <= 0) {
throw new IllegalArgumentException("amount must be positive");
}
}
}

src/main/java/com/bank/account/ Money.java Tutar: değiştirilemeyen bir değer. Para birimini taşır, kuruştan fazla hassasiyeti sessizce yuvarlamak yerine reddeder.

src/main/java/com/bank/account/Money.java
public record Money(BigDecimal amount, Currency currency) {
public Money {
Objects.requireNonNull(amount, "amount");
Objects.requireNonNull(currency, "currency");
// 10.005 TRY is a bug upstream, not something to round away here.
amount = amount.setScale(currency.getDefaultFractionDigits(), RoundingMode.UNNECESSARY);
}
public static Money of(String amount, String currencyCode) {
return new Money(new BigDecimal(amount), Currency.getInstance(currencyCode));
}
public Money plus(Money other) {
requireSameCurrency(other);
return new Money(amount.add(other.amount), currency);
}
public Money minus(Money other) {
requireSameCurrency(other);
return new Money(amount.subtract(other.amount), currency);
}
public Money negate() {
return new Money(amount.negate(), currency);
}
public boolean isLessThan(Money other) {
requireSameCurrency(other);
return amount.compareTo(other.amount) < 0;
}
private void requireSameCurrency(Money other) {
if (!currency.equals(other.currency)) {
throw new IllegalArgumentException("currency mismatch: " + currency + " vs " + other.currency);
}
}
}

src/main/java/com/bank/account/ Movement.java Hesap hareketi: değiştirilemeyen bir kayıt. Zaman Instant olarak tutulur; Date gibi içi değiştirilebilen bir tip dışarı sızmaz.

src/main/java/com/bank/account/Movement.java
// Every field is immutable, so a movement can be shared freely once booked.
public record Movement(Instant bookedAt, Money amount, String description) {
public Movement {
Objects.requireNonNull(bookedAt, "bookedAt");
Objects.requireNonNull(amount, "amount");
Objects.requireNonNull(description, "description");
}
}

src/main/java/com/bank/account/ LeakyAccount.java Karşı örnek: her alana getter ve setter. Derlenir, çalışır ama kuralı koruyan kimse yok.

src/main/java/com/bank/account/LeakyAccount.java
// Counter-example. It compiles and "works", yet nothing keeps the balance
// equal to the sum of the movements or above the overdraft limit.
public class LeakyAccount {
private Money balance;
private List<Movement> movements;
public LeakyAccount(List<Movement> movements) {
this.movements = movements; // keeps the caller's list: they can still change it
}
public Money getBalance() {
return balance;
}
public void setBalance(Money balance) { // any balance, limit or not
this.balance = balance;
}
public List<Movement> getMovements() {
return movements; // the internal list itself: add() here skips the balance
}
}
Kafam karıştı, daha basit anlat

Listenin kendisini dışarı verirsen, alan kişi içine kasiyere sormadan ürün ekleyebilir. Dışarıya kopya ya da salt okunur bir görünüm ver.

Hızlı kontrolBaşlangıç

`private final List<Line> lines;` ve `public List<Line> getLines() { return lines; }`. Alan private ve final. Dışarıdaki kod listeyi değiştirebilir mi?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Her getter'ı, dışarıdaki kodun iç durumu değiştirmesine izin verip vermediğine göre ayır.

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınıflandırılmamış

İç durumu açık bırakır

Dışarıdaki kod iç nesneyi değiştirebilir

    İç durumu korur

    Dışarıdaki kodun değişikliği içeri ulaşmaz

      Kendin gör

      Kapsülleme — invariant hangi kapıdan bozulur?

      Tohum 305269
      Order.java
      1class Order {
      2 private final List<Integer> lines;
      3 private int total;
      4
      5 Order(List<Integer> lines) {
      6 this.lines = lines;
      7 this.total = sum(lines);
      8 }
      9 void addLine(int amount) { lines.add(amount); total += amount; }
      10 List<Integer> getLines() { return lines; }
      11}
      12
      13var amounts = new ArrayList<>(List.of(100, 250));
      14var order = new Order(amounts);
      15order.addLine(40);
      Java 21UTF-8LF
      1. Oynat ya da adımla.
      Hız
      Adım 0

      Şu an ne oldu?

      Order: satırlar ve önbelleğe alınmış bir toplam

      Kural: total her zaman satırların toplamına eşit. addLine() bu kuralı korur. Başka bir kapı var mı?

      Görevler0/3

      • Invariant'ı getter üzerinden bozaçık

        İpucu

        Getter iç listenin kendisini veriyorsa, dışarıdaki kod ona ne yapabilir?

      • getLines() unmodifiableList dönerken bile invariant'ı bozaçık

        İpucu

        unmodifiableList bir kopya değil, bir görünüm. Altındaki listeye başka kimin referansı var?

      • Dışarıdan bir değişiklik girişimine rağmen invariant sağlam kalsınaçık

        İpucu

        İki kapıyı da kapat: içeri alırken kopyala, dışarı verirken koru.

      Olay günlüğü (0)

      Henüz olay yok. Oynat veya adımla.

      1. Varsayılanla oynat. addLine: kural sağlam.
      2. Çağıranı “getLines().add” yap. Kural bozuldu, hata yok.
      3. Getter’ı unmodifiableList yap. Ekleme reddedildi.
      4. Çağıranı “verdiği listeye ekliyor” yap. Getter korumalı, kural yine bozuk.
      5. Constructor’ı “kopyalıyor” yap. Kural sağlam.
      Hızlı kontrolOrta

      `getLines()` artık `Collections.unmodifiableList(lines)` dönüyor, ama constructor `this.lines = lines;` ile çağıranın listesini saklıyor. Çağıran kendi listesine eleman eklerse ne olur?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Satır satır: görünüm ve kopya

      Aynı liste, iki farklı bakış

      View.java
      şu an çalışan satırList<String> tags = new ArrayList<>(List.of("a"));
      2List<String> view = Collections.unmodifiableList(tags);
      3List<String> copy = List.copyOf(tags);
      4
      5tags.add("b");
      6System.out.println(view.size() + " " + copy.size());

      Debug

      Adım 1/5

      main Değiştirilebilir asıl liste.

      tags
      = [a]
      Java 21UTF-8LF1:1

      Sol/sağ ok tuşlarıyla da gezebilirsin.

      Bir unmodifiable viewCollections.unmodifiableList gibi, yazmayı reddeden ama altındaki koleksiyonun değişikliklerini yansıtan salt okunur bir görünüm. Kopya değildir.Sözlükte gör → yalnızca kendi üzerinden yazmayı engeller. Altındaki listeye başka biri yazabiliyorsa, koruma yoktur.

      Kafam karıştı, daha basit anlat

      Salt okunur görünüm, vitrin camı gibidir: sen dokunamazsın ama arkadaki biri rafı değiştirebilir. Kopya ise sana ait ayrı bir raftır.

      Hızlı kontrolOrta

      Program ne yazdırır?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.
      ArrayView.java
      1String[] names = {"ali", "can"};
      2List<String> list = Arrays.asList(names);
      3
      4names[0] = "zeynep";
      5System.out.println(list.get(0));
      Java 21UTF-8LF

      Tuzaklar

      Değiştirilebilen tipler. java.util.Date ya da bir dizi dışarı verilirse, alan kişi içini değiştirebilir. Bunların yerine değiştirilemeyen Instant ve List.copyOf kullan.

      Record da listeyi korumaz. record Order(List<Line> lines) yalnızca hangi listeyi gösterdiğini sabitler, listenin içini değil. Listeyi record’un kurucusunda kopyala.

      Önce kopyala, sonra kontrol et· istersen atla

      Listeyi önce kontrol edip sonra kopyalarsan, bu ikisinin arasında başka bir thread asıl listeyi değiştirebilir. Güvenli sıra tersidir: önce kopyala, sonra kopyayı kontrol et.

      Sessiz kopya. Getter her seferinde bir kopya dönerse, ona eklenen eleman hiçbir yere gitmez ve kimse fark etmez. Salt okunur bir liste ise ekleme denendiği anda hata verir.

      Hızlı kontrolOrta

      Rezervasyonun bitiş tarihi, oluşturulduktan sonra kimse setEnd çağırmadığı hâlde değişiyor. Hangi satırlar?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Hatalı satıra dokun, sonra kontrol et.

      Booking.java
      Java 21UTF-8LF

      Kendini sına

      Şimşek turu1/5

      Her alana getter ve setter açmak kapsülleme demektir.

      Soru 1/2Orta

      Constructor'da doğrulama ve savunmacı kopya birlikte yapılacak. Sıra neden önemli?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Aklında kalacak üç şey

      1. 1 Kapsüllemenin amacı nesnenin kuralını korumaktır. Her alana setter açmak, alanı herkese açmaktan pek farklı değildir.
      2. 2 final bir değişkeni sabitler, gösterdiği nesneyi değil. İç listeyi olduğu gibi döndüren bir getter, private final bir alanı herkese açar.
      3. 3 unmodifiableList bir kopya değil, bir penceredir. Kuralı korumak için içeri alırken kopyalamak, dışarı verirken de korumak gerekir.
      Sonraki kapı Sınıfa bir constructor ekledin ve başka bir dosyadaki new Hesap() birden derlenmez oldu. Ne değişti? Constructor ve this — Nesne Nasıl Doğar? · 8 dk

      3 kart sonraki derste seni bekliyor

      0/5 kart bu dersten toplandı

      Bu dersin üstüne kurulanlar

      Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.