İçeriğe geç

Yaratımsal Kalıplar — Constructor Ne Zaman Yetmez

Orta 10 dk Çok sık karşılaşılır

Önce şunu oku: Value Object ve Record — Anlamı Tipe Taşımak

30 saniyede özet

Bir nesneyi kurmanın birkaç yolu var. İki üç alan için constructor ya da record yeter; nesneye bir ad gerekiyorsa static factory, çok sayıda isteğe bağlı ayar varsa builder. Hangisi olursa olsun, hatalı nesne hiç doğmamalı.

Ödeme servisine bağlanmak 30 saniye sürüyor, cevap beklemek ise 2 saniye. Niyet tam tersiydi. Kodda hata yok, derleyici de bir şey demedi — iki int sadece yer değiştirmişti.

  1. Bayt: Ödeme servisine bağlanmak otuz saniye, cevap beklemek iki saniye sürüyor. Tam tersini istemiştim!

  2. Sen: Kodda hata var mı?

  3. Bayt: Yok. İki sayı sadece yer değiştirmiş, derleyici de fark etmemiş.

  4. Bayt: Kahveciye sayı değil isim söyle: orta, yulaf, iki şot. Nesneler de öyle.

Constructor ne zaman yetmez?

new HttpConfig("https://pay", 30_000, 2_000, 3) — hangi sayının bağlantı süresi olduğunu kodu açmadan söyleyebilir misin? Cevabı göster

Söyleyemezsin. Anlam argümanların sırasına gömülü. Yerlerini değiştirmek de derlenir; hata ancak üretimde görünür.

Sıraya ezberletilen sayılar mı, adıyla söylenen ayarlar mı?
Adım adım oku
  1. Constructor dört değer alıyor ve hangisinin ne olduğu yalnızca sıralarından anlaşılıyor.
  2. İki zaman aşımı yer değiştirirse kod yine derlenir; bağlantı otuz, okuma iki saniye olur.
  3. Builder'da her ayar adıyla verilir: connectTimeout, readTimeout, retries.
  4. build() çağrıldığında nesne bir kez kontrol edilir; hatalı bir nesne hiç doğmaz.

Her isteğe bağlı ayar için bir constructor daha eklemeye telescoping constructorHer isteğe bağlı parametre için bir öncekini çağıran yeni bir constructor eklemek. Çağrı noktası uzun, konumsal ve aynı tipteki argümanlar kolayca yer değiştirir.Sözlükte gör → denir. Az sayıda ve farklı tipte alan için constructor iyidir; çok sayıda isteğe bağlı ayarda değil.

Kafam karıştı, daha basit anlat

new Card(5000, 1000, true, false) yazınca hangi sayının ne olduğu kaybolur. Yerlerini karıştırırsan derleyici de fark etmez.

Hızlı kontrolBaşlangıç

`new HttpConfig("https://pay", 2000, 30000, 3, true, false)` gibi bir çağrının asıl sorunu ne?

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

Ödeme servisine yapılan çağrılar 30 saniye boyunca bağlanmaya çalışıp sonra 2 saniyede okuma timeout'u alıyor. Niyet tam tersiydi. Hangi satırlar sorunun kaynağı?

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

Hatalı satıra dokun, sonra kontrol et.

PaymentClient.java
Java 21UTF-8LF

Static factory: adı olan constructor

Bir static factory methodnew yerine nesneyi döndüren adı olan bir static metot: List.of, Integer.valueOf, Money.of. Hazır bir örnek ya da bir alt tip dönebilir.Sözlükte gör →, new yerine nesneyi döndüren adı olan bir metottur: Duration.ofSeconds(30), List.of(a, b), Money.of(10, TRY).

  • Bir adı var: ofSeconds ile ofMinutes aynı tipte, farklı niyetler.
  • Her seferinde yeni nesne üretmek zorunda değil: Integer.valueOf(127) önbellekten döner.
  • Alt tip dönebilir: List.of uygun bir iç sınıfı seçer.
Kafam karıştı, daha basit anlat

Static factory, adı olan bir kapıdır: Duration.ofSeconds(30) ne yaptığını adıyla söyler. new ise her zaman aynı adı taşır.

Hızlı kontrolOrta

`Money.of(amount, currency)` gibi bir static factory metodunun constructor'a göre üstünlükleri hangisinde doğru sıralanmış?

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

Program ne yazdırır?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.
Cache.java
1Integer a = 127, b = 127;
2Integer c = 1000, d = 1000;
3
4System.out.println(a == b);
5System.out.println(c == d);
Java 21UTF-8LF

Tek örnek: Singleton

Bazen bir şeyden yalnızca bir tane olmalı: tek bir ücret tablosu, tek bir saat. getInstance() da bir static factory’dir; her çağrıda aynı nesneyi verir.

if (instance == null) instance = new RateTable(); — iki thread bu satıra aynı anda gelirse kaç RateTable oluşur? Cevabı göster

İki tane olabilir. İkisi de null görür, ikisi de new der. Kilitsiz tembel kurulum bir yarıştır.

İlk akla gelen çare getInstance()’ı synchronized yapmak. Doğru çalışır, ama nesne kurulduktan sonra bile her çağrı kilitten geçer.

Daha sade iki yol var. Holder idiom işi sınıf yüklemeye bırakır: JVM bir sınıfı tek sefer ve güvenle başlatır. Tek elemanlı enum ise serileştirmede ve reflection’da bile ikinci bir örneğe izin vermez.

src/main/java/com/bank/fee/RateTable.java
public final class RateTable {
private RateTable() { /* load rates once */ }
// Holder idiom: Holder is initialised on first access, once, by the JVM.
private static final class Holder {
static final RateTable INSTANCE = new RateTable();
}
public static RateTable getInstance() { return Holder.INSTANCE; }
}
// Enum singleton: safe against serialization and reflection too.
enum SystemClock { INSTANCE; }
Double-checked locking ve volatile· istersen atla

Double-checked locking önce kilitsiz bakar; null ise kilidi alıp bir kez daha bakar. Alan mutlaka volatile olmalı, yoksa başka bir thread yarım kurulmuş bir nesnenin referansını görebilir. Nedeni yeniden sıralamadır; JMM dersinde adım adım var.

Spring kullanıyorsan çoğu zaman hiçbirine gerek yok. Bean’ler varsayılan olarak singleton scopeVarsayılan bean kapsamı: konteyner başına tek örnek, tüm enjeksiyon noktaları aynı nesneyi paylaşır.Uygulama başına değil, konteyner başına tektir. Paylaşıldığı için singleton bir bean'e istek verisi yazmak, o veriyi tüm kullanıcılara dağıtmak demektir.Sözlükte gör →’tadır: tek örneği konteyner tutar ve sana enjekte eder.

Hızlı kontrolOrta

Yoğun bir sabah loglarda iki kez "ücret tablosu yüklendi" görüyorsun, oysa RateTable tek olmalıydı. Hangi satırlar?

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

Hatalı satıra dokun, sonra kontrol et.

RateTable.java
Java 21UTF-8LF

Double-checked locking ile yazılmış bir singleton'da `instance` alanı neden `volatile` olmalı?

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

Kendin gör

Nesne oluşturma — constructor ne zaman yetmez?

Tohum 280788
Teleskop constructor.java
1var config = new HttpConfig("https://pay", 2_000, 30_000, 3, true, true);
Java 21UTF-8LF
  • Çağrı okunur mubekliyor
  • Yer değiştiren argüman derlenir mibekliyor
  • Nesne değiştirilemez mibekliyor
  • Alanlar arası kural tek yerde denetlenir mibekliyor
  • Önbellekten ya da alt tip dönebilir mibekliyor
  • Yazılacak ek kodbekliyor
Hız
Adım 0

Şu an ne oldu?

Teleskop constructor, 5 isteğe bağlı ayar

Aynı HttpConfig nesnesi altı ölçüte göre değerlendirilecek.

Görevler0/4

  • İki argümanın yer değiştirmesinin derlendiği bir çağrı üretaçık

    İpucu

    Konumsal argümanlar ve aynı tipte yan yana değerler.

  • Nesnenin yarım hâlde görülebildiği bir kurulum seçaçık

    İpucu

    Önce boş nesne, sonra alanlar tek tek.

  • Builder'ın gereksiz olduğu bir durum bulaçık

    İpucu

    Builder her zaman güvenlidir — ama her zaman gerekli değildir.

  • 6+ isteğe bağlı ayar için bütün ölçütleri yeşile çeviraçık

    İpucu

    Adı olan argümanlar, tek bir doğrulama anı ve değiştirilemez bir sonuç.

Olay günlüğü (0)

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

  1. Varsayılanla oynat. Teleskop constructor, beş ayar: okunmuyor ve argümanlar yer değiştirebiliyor.
  2. Setter’ları seç. Adlar var, ama nesne yarım hâlde dolaşıyor.
  3. Builder’a geç. Bütün ölçütler yeşil.
  4. Ayar sayısını 2 yap. Builder hâlâ doğru, ama kazancından pahalı.
Hızlı kontrolOrta

Her durum için en uygun oluşturma biçimini seç.

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

Sınıflandırılmamış

Constructor / record

Az sayıda, zorunlu, farklı anlamlı alan

    Static factory

    Adı olan kurulum, önbellek ya da alt tip

      Builder

      Çok sayıda isteğe bağlı ayar

        Factory (çalışma anında seçim)

        Hangi uygulamanın kurulacağı veriye göre değişiyor

          Satır satır: doğrulayan bir builder

          Geçersiz nesne hiç oluşmaz

          HttpConfig.java
          1public record HttpConfig(String baseUrl, Duration connect, Duration read, int retries) {
          2
          3 public HttpConfig {
          4 if (connect.compareTo(read) > 0) throw new IllegalArgumentException("connect > read");
          5 }
          6
          şu an çalışan satır public static Builder builder(String baseUrl) { return new Builder(baseUrl); }
          8
          9 public static final class Builder {
          10 private final String baseUrl;
          11 private Duration connect = Duration.ofSeconds(2), read = Duration.ofSeconds(30);
          12 private int retries = 0;
          13 private Builder(String baseUrl) { this.baseUrl = baseUrl; }
          14 public Builder read(Duration value) { this.read = value; return this; }
          15 public HttpConfig build() { return new HttpConfig(baseUrl, connect, read, retries); }
          16 }
          17}

          Debug

          Adım 1/5

          çağıran Zorunlu değer builder'ın başlangıcında isteniyor: baseUrl'süz bir kurulum başlayamaz.

          Java 21UTF-8LF7:1

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

          builderÇok sayıda isteğe bağlı ayarı adıyla toplayıp nesneyi tek seferde, doğrulayarak kuran yardımcı. Geçersiz ya da yarım nesne hiç oluşmaz.Sözlükte gör →’ın asıl değeri adlar ve tek doğrulama anıdır. Kuralı record’un compact constructor’ına koy: builder da, doğrudan new de aynı kapıdan geçer.

          Banka kartındaki ayarlar da böyle bir kahve siparişine benzer: günlük harcama limiti, ATM limiti, temassız limit, internet ve yurt dışı izni. Gel, bunları adıyla kuran küçük bir örneğe bakalım.

          Derinleş · Kart ayarları: static factory ve builder 4 dosya · ~87 satır · ilk okumada atlayabilirsin
          Proje dosyaları

          src/main/java/com/bank/card/ CardControls.java Kural record'un kurucusunda; static factory ise sık kullanılan kuruluma bir ad verir.

          src/main/java/com/bank/card/CardControls.java
          public record CardControls(
          Money dailyPurchaseLimit,
          Money dailyAtmLimit,
          Money contactlessLimit, // per transaction, no PIN asked
          boolean onlineEnabled,
          boolean abroadEnabled) {
          public CardControls {
          Currency currency = dailyPurchaseLimit.currency();
          if (!dailyAtmLimit.currency().equals(currency) || !contactlessLimit.currency().equals(currency)) {
          throw new IllegalArgumentException("all limits must share one currency");
          }
          if (contactlessLimit.amount().compareTo(dailyPurchaseLimit.amount()) > 0) {
          throw new IllegalArgumentException("contactless limit above daily purchase limit");
          }
          }
          // A named starting point; the amounts come from the card product, not from here.
          public static CardControls standardFor(CardProduct product) {
          return new CardControls(product.purchaseLimit(), product.atmLimit(),
          product.contactlessLimit(), true, false);
          }
          public static CardControlsBuilder builder(CardProduct product) {
          return new CardControlsBuilder(standardFor(product));
          }
          }

          src/main/java/com/bank/card/ CardControlsBuilder.java Builder her ayarı adıyla toplar ve sonunda aynı kurucudan geçer.

          src/main/java/com/bank/card/CardControlsBuilder.java
          public final class CardControlsBuilder {
          private Money dailyPurchaseLimit;
          private Money dailyAtmLimit;
          private Money contactlessLimit;
          private boolean onlineEnabled;
          private boolean abroadEnabled;
          // Starts from the product's defaults; the caller changes only what differs.
          CardControlsBuilder(CardControls start) {
          this.dailyPurchaseLimit = start.dailyPurchaseLimit();
          this.dailyAtmLimit = start.dailyAtmLimit();
          this.contactlessLimit = start.contactlessLimit();
          this.onlineEnabled = start.onlineEnabled();
          this.abroadEnabled = start.abroadEnabled();
          }
          public CardControlsBuilder dailyPurchaseLimit(Money value) { this.dailyPurchaseLimit = value; return this; }
          public CardControlsBuilder dailyAtmLimit(Money value) { this.dailyAtmLimit = value; return this; }
          public CardControlsBuilder contactlessLimit(Money value) { this.contactlessLimit = value; return this; }
          public CardControlsBuilder onlineEnabled(boolean value) { this.onlineEnabled = value; return this; }
          public CardControlsBuilder abroadEnabled(boolean value) { this.abroadEnabled = value; return this; }
          // Same door as new: the record's compact constructor checks everything once.
          public CardControls build() {
          return new CardControls(dailyPurchaseLimit, dailyAtmLimit, contactlessLimit,
          onlineEnabled, abroadEnabled);
          }
          }

          src/main/java/com/bank/card/ CardSettingsService.java Müşteri kartı yalnızca yurt içi mağazalara açıyor; her satır ne dediğini söylüyor.

          src/main/java/com/bank/card/CardSettingsService.java
          @Service
          public class CardSettingsService {
          private final CardRepository cards;
          CardSettingsService(CardRepository cards) {
          this.cards = cards;
          }
          @Transactional
          public void limitToDomesticInStore(CardId cardId, Money atmLimit) {
          Card card = cards.findById(cardId).orElseThrow();
          CardControls controls = CardControls.builder(card.product())
          .dailyAtmLimit(atmLimit)
          .abroadEnabled(false)
          .onlineEnabled(false)
          .build();
          card.apply(controls);
          }
          }

          src/main/java/com/bank/card/ LegacyCardControls.java Şöyle de yazılabilirdi: sıralı bir constructor. Derlenir, ama iki limitin yeri kolayca karışır.

          src/main/java/com/bank/card/LegacyCardControls.java
          // The positional version: it compiles, and the call site does not say which Money is which.
          public class LegacyCardControls {
          public LegacyCardControls(Money dailyPurchaseLimit, Money dailyAtmLimit, Money contactlessLimit,
          boolean onlineEnabled, boolean abroadEnabled) {
          // ...
          }
          }
          // Somewhere else: ATM and purchase limits swapped, two booleans nobody can read.
          // new LegacyCardControls(atmLimit, purchaseLimit, contactless, false, true);
          Kafam karıştı, daha basit anlat

          Builder bir kahve siparişi gibidir: “şeker yok, süt var” diye adıyla söylersin. Sipariş en sonda tek bir yerde kontrol edilir.

          Hızlı kontrolOrta

          Bir builder'da "connectTimeout, readTimeout'tan büyük olamaz" kuralı nerede denetlenmeli?

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

          Tuzaklar

          İki alan için builder. Record ya da constructor yeterken builder yalnızca kod ekler.

          Boş constructor + setter. Nesne setter’lar arasında geçersiz ve her zaman değiştirilebilir.

          Doğrulamayı atlayan üretilmiş builder. Lombok @Builder kuralları kendiliğinden bilmez; kuralı constructor’a koy.

          Static factory ile Factory Method’u karıştırmak. GoF kalıbında hangi nesnenin oluşacağına alt sınıf karar verir; Spring’de bu işi çoğu zaman konteyner yapar.

          Hızlı kontrolOrta

          GoF'un Factory Method kalıbı ile bir static factory metodu arasındaki fark ne?

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

          Kendini sına

          Şimşek turu1/5

          Aynı tipte iki constructor parametresinin yeri değişirse derleyici uyarır.

          Soru 1/3İleri

          Boş constructor + setter'larla kurulan bir yapılandırma nesnesi, uygulama açılırken bir thread'de dolduruluyor ve başka thread'lerle paylaşılıyor. Builder + final alanlara geçmenin en önemli kazancı ne?

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

          Aklında kalacak üç şey

          1. 1 Sırayla verilen argümanlar anlamı sıraya gömer. Aynı tipteki iki argüman yer değiştirse de kod derlenir.
          2. 2 Static factory'nin bir adı vardır, hazırdakini geri verebilir ve bir alt tip döndürebilir. Integer.valueOf ve List.of bunun örnekleri.
          3. 3 Builder çok sayıda isteğe bağlı ayarı adıyla toplar ve nesneyi build() anında bir kez kontrol eder. İki üç alan için gereksiz yüktür.
          Sonraki kapı Bir nesnenin koduna dokunmadan ona yeni bir davranış nasıl eklersin? Yapısal Kalıplar — Sarmalayarak Genişletmek · 8 dk

          5 kart sonraki derste seni bekliyor

          0/5 kart bu dersten toplandı