İçeriğe geç

Tasarım Kalıpları — Ne Zaman Kazandırır

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

Önce şunu oku: Nesne Yönelimli Tasarım — Kapsülleme ve Kompozisyon

30 saniyede özet

Tasarım kalıpları icat edilmedi, fark edildi: farklı ekipler aynı soruna aynı çözümü bulunca ona bir ad verildi. Her kalıp bir şeyi ucuzlatır, başka bir şeyi pahalılaştırır.

Kalıp öğrenmenin en yaygın yan etkisi, her yerde uygulanacak yer aramaktır. Oysa kalıplar icat edilmedi — fark edildi. Farklı ekipler aynı probleme aynı çözümü bulunca ona bir ad verildi.

Önce sorun, sonra aynı çözüm, en son ad.
Adım adım oku
  1. Dar bir sokakta her evin üst katındaki oda küçük kalıyor.
  2. Birbirini tanımayan ustalar aynı çözümü buluyor: üst kat sokağa doğru taşıyor.
  3. Çözüm o kadar yaygınlaşıyor ki ona bir ad veriliyor: cumba.
  4. Artık iki kişi tek kelimeyle anlaşıyor. Tasarım kalıpları da böyle doğdu.
  1. Bayt: Bütün tasarım kalıplarını ezberledim. Şimdi hepsini projeye koyacağım!

  2. Sen: Hepsini mi? Neden?

  3. Bayt: Kalıplar icat edilmedi, fark edildi. Önce sorun gelir, sonra çözüm, en son ad.

  4. Bayt: Adı bilmenin asıl değeri tek kelimeyle anlaşmak. Kullanmak için bir sebep değil!

Kalıp sana iki şey verir

Bir kalıp sana iki şey verir: bir ad ve bir takas.

Ad, iletişim aracıdır. “Burada Strategy var” demek, karşı tarafa on satırlık açıklama yapmaya bedeldir — ama yalnızca o da adı biliyorsa.

Takas, asıl konudur. Hiçbir kalıp maliyeti yok etmez; bir yerden alıp başka yere koyar.

Strategy yeni tip eklemeyi ucuzlatır ve tüm tiplere yeni işlem eklemeyi pahalılaştırır. Bu, kalıbın kusuru değil, tanımıdır.

Kafam karıştı, daha basit anlat

Kalıp, sık yaşanan bir soruna verilmiş kısa bir addır. “Strategy” dediğinde karşındaki bütün fikri anlar, sen de uzun uzun anlatmak zorunda kalmazsın.

Hızlı kontrolBaşlangıç

Bir tasarım kalıbı temelde nedir?

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

Strategy ile Factory arasındaki fark nedir?

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

Gerçekten işe yarayan birkaçı

Yirmi üç kalıbı ezberlemek gereksiz. Günlük işte tekrar tekrar karşına çıkan birkaçı var:

KalıpNe yaparNe zaman kazandırır
StrategyDavranışı ayrı bir nesneye taşırAynı işin birden çok yapılış biçimi var ve yenisi ekleniyor
DecoratorNesneyi aynı arayüzle sarmalarDavranışı biriktirmek gerekiyor: cache + retry + log
AdapterUyuşmayan iki arayüzü bağlarDış kütüphanenin şekli senin modeline uymuyor
FacadeKarmaşık bir alt sistemi tek kapıya indirirÇağıranın bilmesi gereken şey, var olan API’den az
ObserverDeğişimi bilmesi gerekene haber verirYayıncının aboneleri tanımaması gerekiyor

Adapter ile Facade sık karıştırılır: Adapter şekli değiştirir, Facade yüzeyi küçültür. Adapter’da karşı taraf zaten doğru işi yapıyordur, imzası uymuyordur; Facade’da işler doğrudur ama çağıranın görmesi gereken çok fazla şey vardır.

Kafam karıştı, daha basit anlat

Adapter bir priz dönüştürücüsüdür: fişin şeklini değiştirir. Facade ise tek düğmeli bir kumandadır: arkadaki karmaşık düğmeleri saklar.

Hızlı kontrolOrta

Adapter ile Facade'ı ayıran şey nedir?

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

Satır satır: switch ne zaman Strategy’yi hak eder

Üç kanallı bir bildirim dağıtıcısında `switch`'i Strategy'ye çevirmek kaç dosya açılışını önler? Cevabı göster

Sıfır — Strategy tek başına hiçbir şey önlemez. Dağıtıcı hâlâ “hangi tip hangi sınıf” eşlemesini bildiği için yeni kanal yine bir dosya açtırır. Kazanç ancak eşlemeyi de dışarı çıkarınca gelir.

Kalıp maliyeti nereye taşıyor?

Notifier.java
1void send(Notification n) {
şu an çalışan satır switch (n.channel()) {
3 case EMAIL -> email.send(n);
4 case SMS -> sms.send(n);
5 case PUSH -> push.send(n);
6 }
7}
8
9// Strategy: davranış kendi dosyasında
10interface Channel { void send(Notification n); }
11
12// ama dağıtıcı hâlâ eşlemeyi biliyor
13Map<Type, Channel> byType = Map.of(EMAIL, email, SMS, sms);

Debug

Adım 1/6

bugün Üç kanal, tek dosya. Akışı izlemek için tek yere bakıyorsun — bu gerçek bir avantaj, kusur değil.

dosya
= 1
okuma
= 1 yer
Java 21UTF-8LF2:1

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

Bu takasın adı expression problemBir tasarımın ya yeni tip eklemeyi ya yeni işlem eklemeyi ucuzlatabilmesi, ikisini birden ucuzlatamaması.Polimorfizm yeni tipi ucuzlatır: yeni sınıf yazarsın, çağıran değişmez. switch ise yeni işlemi ucuzlatır: tek yerde tüm tipleri ele alırsın. Hangisinin daha sık olacağını bilmek, kalıp kararını verir.Sözlükte gör →: polimorfizm yeni tip eklemeyi ucuzlatır, switch yeni işlem eklemeyi. İkisini birden ucuzlatan bir tasarım yok.

Bir bankada para gönderirken de aynı soru çıkar: FAST mı, EFT mi, SWIFT mi? Gel, her kanalın kendi dosyasında yaşadığı küçük bir örneğe bakalım.

Derinleş · Havale kanalları: Strategy ve registry 4 dosya · ~77 satır · ilk okumada atlayabilirsin
Proje dosyaları

src/main/java/com/bank/transfer/ TransferRail.java Her ödeme kanalının uyduğu küçük sözleşme.

src/main/java/com/bank/transfer/TransferRail.java
public interface TransferRail {
Rail rail();
// Adding a method here opens every rail class: the price of this pattern.
TransferResult send(OutgoingTransfer transfer);
}

src/main/java/com/bank/transfer/ FastRail.java FAST kanalı kendi kurallarını kendisi bilir; yeni bir kanal da böyle tek bir yeni dosya olur.

src/main/java/com/bank/transfer/FastRail.java
@Component
final class FastRail implements TransferRail {
private final FastClient client;
private final BigDecimal maxAmount; // per-transfer ceiling set by the central bank
FastRail(FastClient client, @Value("${rails.fast.max-amount-try}") BigDecimal maxAmount) {
this.client = client;
this.maxAmount = maxAmount;
}
@Override
public Rail rail() {
return Rail.FAST;
}
@Override
public TransferResult send(OutgoingTransfer transfer) {
if (!transfer.amount().currency().equals(Currency.getInstance("TRY"))) {
throw new IllegalArgumentException("FAST carries TRY only");
}
if (transfer.amount().value().compareTo(maxAmount) > 0) {
throw new IllegalArgumentException("amount above FAST limit");
}
return client.submit(transfer.debtorIban(), transfer.creditorIban(), transfer.amount().value());
}
}

src/main/java/com/bank/transfer/ TransferDispatcher.java Dağıtıcı kanalların adını bilmez, Spring hepsini bulup ona verir.

src/main/java/com/bank/transfer/TransferDispatcher.java
@Service
public class TransferDispatcher {
private final Map<Rail, TransferRail> byRail;
// Spring injects every TransferRail bean; a new rail is a new @Component, nothing more.
TransferDispatcher(List<TransferRail> rails) {
this.byRail = rails.stream()
.collect(Collectors.toUnmodifiableMap(TransferRail::rail, Function.identity()));
// toUnmodifiableMap throws on a duplicate key, so two beans for one rail fail at startup.
}
public TransferResult dispatch(OutgoingTransfer transfer) {
TransferRail rail = byRail.get(transfer.rail());
if (rail == null) {
throw new IllegalStateException("no rail registered for " + transfer.rail());
}
return rail.send(transfer);
}
}

src/main/java/com/bank/transfer/ SwitchTransferDispatcher.java Şöyle de yazılabilirdi: tek bir switch. Üç kanalda gayet sade, ama her yeni kanalda bu dosyayı yeniden açarsın.

src/main/java/com/bank/transfer/SwitchTransferDispatcher.java
// The switch version: perfectly fine while the rails stay few and fixed.
@Service
public class SwitchTransferDispatcher {
private final FastClient fast;
private final EftClient eft;
private final SwiftGateway swift;
SwitchTransferDispatcher(FastClient fast, EftClient eft, SwiftGateway swift) {
this.fast = fast;
this.eft = eft;
this.swift = swift;
}
public TransferResult dispatch(OutgoingTransfer t) {
// Every rail is wired here; a new rail means editing this tested method.
return switch (t.rail()) {
case FAST -> fast.submit(t.debtorIban(), t.creditorIban(), t.amount().value());
case EFT -> eft.submit(t.debtorIban(), t.creditorIban(), t.amount().value());
case SWIFT -> swift.sendMt103(t.debtorIban(), t.creditorIban(), t.creditorBic(), t.amount());
};
}
}
Hızlı kontrolOrta

Bir `switch`'i Strategy'ye çevirdin ama yeni tip eklemek hâlâ var olan bir dosyayı açtırıyor. Neden?

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

Her ihtiyacı, çözümü sarmalamak mı yoksa davranışı ayırmak mı olduğuna göre ayır.

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

Sınıflandırılmamış

Sarmalama (Decorator, Adapter, Facade)

Var olan bir şeyin önüne geçiliyor

    Davranışı ayırma (Strategy, Observer)

    İşin kendisi değişken hâle getiriliyor

      Kendin gör

      Aynı dağıtıcı, dört tasarım

      Tohum 7
      1. Tek switch
      2. Strategy
      3. Kendini kaydeden registry
      4. Kalıp yığını

      Tek dosyada bir switch, üç dal.

      Dosya
      1
      Yeni kanal açtırıyor
      1 dosya
      Yeni işlem açtırıyor
      1 dosya
      Akışı izlemek için
      1 yer
      Beklenen değişim karışımında toplam dosya açılışı
      TasarımAçılan dosyaOkuma
      Tek switch141
      Strategy203
      Kendini kaydeden registryen ucuz83
      Kalıp yığını187
      Hız
      Adım 0

      Şu an ne oldu?

      Tek dosya, tek switch

      Her yeni kanal bu çalışan, test edilmiş dosyayı açtırıyor — beklenen karışımda toplam 14 dosya açılışı. Ama akışı izlemek için tek yere bakıyorsun. Bu karışımda Kendini kaydeden registry daha ucuz (8 dosya).

      Aklında kalsın: Üç sabit tip için switch bir kusur değil. Maliyet, dosyanın ne sıklıkta yeniden açıldığında doğar.

      Olay günlüğü (0)

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

      Sırayla dene:

      1. Başlangıçta tek switch var. “Eklenecek yeni kanal”ı 2’ye indir — en ucuz tasarım hangisi?
      2. Kanalı 12’ye çıkar. Şimdi registry kazanıyor: kalıp kendini ödemeye başladığı nokta burası.
      3. Kanalı 12’de bırakıp “yeni işlem”i 8’e çıkar. Kazanan yine switch — expression problem’i sayılarla görüyorsun.
      4. “Kalıp yığ”a bas ve tabloyu incele: hiçbir karışımda kazanmıyor. Bir kalıp takas yapmıyorsa sadece dolaylılıkBir şeye doğrudan değil, araya giren bir katman üzerinden ulaşmak. Esneklik verir, okurken bir adım daha attırır — bedava değildir.Sözlükte gör → ekliyor demektir.

      Tuzaklar

      Singleton. Kalıp listesindeki en popüler ve en zararlı madde: global durum yaratır, testte izole edilemez, bağımlılığı imzada gizler.

      Bugün ihtiyacın olan şey neredeyse her zaman “tek örnek”tir, “global erişim” değil — onu da DI container zaten veriyor.

      Kalıp önce, problem sonra. Tasarıma “hangi kalıbı kullansam” diye başlamak, çözümü probleme uydurmaktır. Doğru sıra: değişim nereden gelecek, sonra hangi takas.

      İsimlendirme kargaşası. OrderStrategyFactoryImpl gibi adlar kalıbı anlatır ama işi anlatmaz. Sınıf adı ne yaptığını söylemeli; kalıbın adı gerekiyorsa yorumda durabilir.

      Kafam karıştı, daha basit anlat

      Singleton’ın asıl sorunu tek olması değil, herkesin her yerden ona ulaşabilmesidir. Tek bir örnek istiyorsan bunu Spring’e bırak, o zaten tek tane kurar.

      Kendini sına

      Şimşek turu1/5

      Tasarım kalıpları, kullanılsın diye önceden icat edilmiş kurallardır.

      Soru 1/3Orta

      Bu program ne yazdırır?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.
      SarmalamaSirasi.java
      1interface Text { String render(); }
      2
      3record Plain(String value) implements Text {
      4 public String render() { return value; }
      5}
      6
      7record Bold(Text inner) implements Text {
      8 public String render() { return "<b>" + inner.render() + "</b>"; }
      9}
      10
      11record Trim(Text inner) implements Text {
      12 public String render() { return inner.render().trim(); }
      13}
      14
      15System.out.println(new Trim(new Bold(new Plain(" hi "))).render());
      16System.out.println(new Bold(new Trim(new Plain(" hi "))).render());
      Java 21UTF-8LF

      Aklında kalacak üç şey

      1. 1 Bir kalıp maliyeti yok etmez, yerini değiştirir. Strategy yeni tip eklemeyi ucuzlatır, bütün tiplere yeni işlem eklemeyi pahalılaştırır.
      2. 2 Kalıbın adını bilmek onu kullanmak için sebep değildir. Sebep her zaman, değişikliğin hangi yönden geleceğidir.
      3. 3 Kalıp isimlerinin asıl değeri anlaşmaktır: 'burada Strategy var' demek on satır açıklamanın yerini tutar.
      Sonraki kapı Kural bir serviste yaşıyorsa, o servisi atlayan kod kuralı da atlar mı? Zengin Domain Modeli — JPA Entity'de Kapsülleme · 10 dk

      5 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.