Tasarım Desenleri (Design Patterns)
Yazılımcılar yıllar içinde aynı problemlerle defalarca karşılaştı: "tek bir örnek nasıl garanti edilir?", "nesne oluşturma mantığı nasıl gizlenir?", "uyumsuz iki arayüz nasıl birleştirilir?", "bir algoritma çalışma za…
Tasarım Desenleri (Design Patterns)
Yazılımcılar yıllar içinde aynı problemlerle defalarca karşılaştı: "tek bir örnek nasıl garanti edilir?", "nesne oluşturma mantığı nasıl gizlenir?", "uyumsuz iki arayüz nasıl birleştirilir?", "bir algoritma çalışma zamanında nasıl değiştirilir?". Tasarım desenleri, bu tekrarlayan problemlere verilmiş, denenmiş ve isimlendirilmiş çözüm kalıplarıdır. Onları bilmek hem daha sağlam kod yazmanı hem de diğer geliştiricilerle ortak bir dil konuşmanı sağlar. Bu bölümde her üç kategoriden, gerçek hayat senaryolarıyla toplam dokuz deseni göreceğiz.
Desenler birer "kopyala-yapıştır kod" değil, fikirdir; probleme göre uyarlarsın. Ayrıca her yere desen serpiştirmek de hatadır — deseni ihtiyaç doğunca, yerinde kullan.
Üç kategori
Klasik (Gang of Four) sınıflandırma üç gruptur:
- Yaratımsal (creational): Nesne oluşturmayı yönetir — Singleton, Factory Method, Builder.
- Yapısal (structural): Nesneleri/sınıfları bir araya getirir — Adapter, Decorator, Facade.
- Davranışsal (behavioral): Nesneler arası iletişimi düzenler — Strategy, Observer, Command.
Yaratımsal desenler
Örnek 1 (./Ornek1.java) bir bildirim sistemi üzerinden üç deseni gösterir:
- Singleton: Bir sınıfın tüm uygulamada tek örneği olmasını garanti eder; yapılandırma,
loglama, bağlantı havuzu gibi durumlarda kullanılır. Anahtarı
privateconstructor ve tek statik örnektir. (Spring'de bean'ler zaten varsayılan olarak singleton'dır; framework bunu senin yerine yönetir.) - Factory Method: Nesne oluşturma kararını tek yerde toplar; çağıran kod somut sınıfları
bilmez, sadece "bana bir
emailbildirimi ver" der. - Builder: Çok sayıda (özellikle opsiyonel) alanı olan nesneyi okunaklı, zincirleme kurar ve uzun/karışık constructor'lardan kurtarır.
Yapısal desenler
Örnek 2 (./Ornek2.java) üç yapısal deseni gerçek senaryolarla gösterir:
- Adapter: Uyumsuz bir arayüzü, beklediğin arayüze çevirir. Elindeki eski/üçüncü-parti bir
ödeme API'sini (
makePayment(kurus)) yeni arayüzüne (ode(tutar, paraBirimi)) uyarlar. Eski kodu değiştiremediğinde hayat kurtarır. - Decorator: Bir nesneye, sınıfını değiştirmeden çalışma zamanında davranış/özellik ekler.
Sade kahveyi süt ve şekerle "sarmalayarak" hem açıklamasını hem fiyatını büyütürüz.
(Java'nın I/O akışları —
BufferedReader(new FileReader(...))— bu desenin ta kendisidir.) - Facade: Karmaşık bir alt sistemi (stok + ödeme + kargo) tek, basit bir arayüzün
(
siparisVer) arkasına gizler; istemciyi ayrıntıdan korur.
Davranışsal desenler
Örnek 3 (./Ornek3.java) üç davranışsal deseni uygular:
- Strategy: Bir algoritmayı, onu kullanan koddan ayırıp çalışma zamanında değiştirilebilir
kılar. Kargo ücreti hesabını
if/elseyığınına gömmek yerine her stratejiyi ayrı tutarız; functional interface ve lambda'lar bunu neredeyse bedava hale getirir. - Observer: Bir nesnenin durumu değişince ona abone olanların otomatik haberdar
olmasını sağlar. Stok tükenince/gelince tüm aboneler (müşteri, analitik) bilgilendirilir.
(Spring'in
ApplicationEvent/@EventListenermekanizması da özünde Observer'dır.) - Command: Bir işlemi nesneye dönüştürür; böylece onu saklayabilir, sıraya koyabilir ve geri alabilirsin (undo). Metin düzenleyicide her yazma bir komuttur ve geçmişten geri alınabilir.
Desenler ve Spring
Dikkat ettiysen, neredeyse her desenin yanında "Spring'de şöyle kullanılır" notu var. Bu
tesadüf değil: Spring, bu desenlerin büyük ve zarif bir uygulamasıdır — bean'ler Singleton,
context Factory, @EventListener Observer, AOP proxy'leri Proxy/Decorator, RestTemplate
yapılandırması Builder mantığıyla çalışır. Bu desenleri tanımak, Spring'i "sihir" olarak değil,
tanıdık mühendislik fikirleri olarak görmeni sağlar.
Özet
Üç kategoriden dokuz deseni gerçek senaryolarla gördük: Singleton, Factory, Builder (yaratımsal); Adapter, Decorator, Facade (yapısal); Strategy, Observer, Command (davranışsal). Ortak temaları, değişimi yalıtmak ve gevşek bağlılık kurmaktır — OOP ve soyutlama bölümlerinde başlayan yolculuğun doğal devamı.
Bu bölümle Java'nın temelini ve ekosistemini büyük ölçüde tamamladın. Artık nesneler, arayüzler, gevşek bağlılık, reflection ve desenler senin için tanıdık. Sırada, dilin yıllar içinde nasıl evrildiğine bakıyoruz: Java sürüm evrimi (8 → 25).
▶ Kod Örnekleri(3)
Ornek1
çalıştırılabilir1// Ornek1: Yaratımsal (creational) desenler — Singleton, Factory Method, Builder.
2// Gerçek senaryo: bir bildirim sistemi (e-posta/SMS/push) ve mesaj kurulumu.
3// Çalıştırma: java Ornek1.java
4public class Ornek1 {
5
6 public static void main(String[] args) {
7 // --- SINGLETON: tüm uygulamada tek yapılandırma örneği ---
8 Yapilandirma.getInstance().ayarla("gonderen", "destek@site.com");
9 System.out.println("Singleton gönderen: " + Yapilandirma.getInstance().oku("gonderen"));
10
11 // --- FACTORY METHOD: tipe göre doğru bildirim nesnesini üret ---
12 // Çağıran kod somut sınıfları (EmailBildirim vb.) bilmez; sadece tip söyler.
13 System.out.println("\nFactory ile bildirimler:");
14 for (String kanal : new String[]{"email", "sms", "push"}) {
15 Bildirim b = BildirimFabrikasi.olustur(kanal);
16 b.gonder("Siparişiniz kargoda!");
17 }
18
19 // --- BUILDER: çok alanlı bir mesajı okunaklı, adım adım kur ---
20 System.out.println("\nBuilder ile e-posta:");
21 Eposta eposta = new Eposta.Builder("ada@site.com")
22 .konu("Hoş geldin")
23 .govde("Aramıza katıldığın için teşekkürler.")
24 .onemli(true)
25 .build();
26 System.out.println(" " + eposta);
27 }
28}
29
30// Singleton: private constructor + tek statik örnek (thread-safe, sınıf yüklenince oluşur).
31class Yapilandirma {
32 private static final Yapilandirma TEK = new Yapilandirma();
33 private final java.util.Map<String, String> degerler = new java.util.HashMap<>();
34 private Yapilandirma() {}
35 static Yapilandirma getInstance() { return TEK; }
36 void ayarla(String k, String v) { degerler.put(k, v); }
37 String oku(String k) { return degerler.get(k); }
38}
39
40// Factory Method: nesne üretim kararını tek yerde toplar.
41interface Bildirim { void gonder(String mesaj); }
42class EmailBildirim implements Bildirim { public void gonder(String m) { System.out.println(" [e-posta] " + m); } }
43class SmsBildirim implements Bildirim { public void gonder(String m) { System.out.println(" [SMS] " + m); } }
44class PushBildirim implements Bildirim { public void gonder(String m) { System.out.println(" [push] " + m); } }
45
46class BildirimFabrikasi {
47 static Bildirim olustur(String kanal) {
48 return switch (kanal) {
49 case "email" -> new EmailBildirim();
50 case "sms" -> new SmsBildirim();
51 case "push" -> new PushBildirim();
52 default -> throw new IllegalArgumentException("Bilinmeyen kanal: " + kanal);
53 };
54 }
55}
56
57// Builder: opsiyonel alanları çok olan nesneyi zincirleme, okunaklı kur.
58class Eposta {
59 private final String alici, konu, govde;
60 private final boolean onemli;
61 private Eposta(Builder b) { this.alici = b.alici; this.konu = b.konu; this.govde = b.govde; this.onemli = b.onemli; }
62 @Override public String toString() {
63 return (onemli ? "[ÖNEMLİ] " : "") + alici + " | " + konu + " | " + govde;
64 }
65 static class Builder {
66 private final String alici;
67 private String konu = "(konusuz)";
68 private String govde = "";
69 private boolean onemli = false;
70 Builder(String alici) { this.alici = alici; }
71 Builder konu(String k) { this.konu = k; return this; }
72 Builder govde(String g) { this.govde = g; return this; }
73 Builder onemli(boolean o) { this.onemli = o; return this; }
74 Eposta build() { return new Eposta(this); }
75 }
76}Ornek2
çalıştırılabilir1// Ornek2: Yapısal (structural) desenler — Adapter, Decorator, Facade.
2// Gerçek senaryo: eski bir ödeme API'sini uyarlama, kahve fiyatı süsleme, sipariş cephesi.
3// Çalıştırma: java Ornek2.java
4public class Ornek2 {
5
6 public static void main(String[] args) {
7 // --- ADAPTER: uyumsuz bir arayüzü, beklediğimiz arayüze uyarla ---
8 // Yeni kodumuz OdemeServisi bekliyor ama elimizde eski, farklı imzalı bir API var.
9 OdemeServisi odeme = new EskiOdemeAdapter(new EskiOdemeApi());
10 System.out.println("Adapter ile ödeme:");
11 odeme.ode(250.0, "TRY");
12
13 // --- DECORATOR: bir nesneye, sınıfını değiştirmeden davranış/özellik ekle ---
14 // Temel kahveyi sarmalayarak süt ve şeker "ekliyoruz"; her sarmalayıcı fiyatı artırır.
15 System.out.println("\nDecorator ile kahve:");
16 Kahve kahve = new SekerEkle(new SutEkle(new SadeKahve()));
17 System.out.printf(" %s = %.2f TL%n", kahve.aciklama(), kahve.fiyat());
18
19 // --- FACADE: karmaşık bir alt sistemi tek, basit bir arayüzün arkasına gizle ---
20 // İstemci stok/ödeme/kargo ayrıntılarıyla uğraşmaz; tek bir siparisVer() çağırır.
21 System.out.println("\nFacade ile sipariş:");
22 SiparisFacade siparis = new SiparisFacade();
23 siparis.siparisVer("Klavye", 250.0);
24 }
25}
26
27// --- Adapter ---
28interface OdemeServisi { void ode(double tutar, String paraBirimi); }
29
30class EskiOdemeApi { // değiştiremediğimiz eski/3. parti API
31 void makePayment(int kurus, String currency) {
32 System.out.println(" [eski API] " + kurus + " kuruş (" + currency + ") tahsil edildi.");
33 }
34}
35
36class EskiOdemeAdapter implements OdemeServisi { // eskiyi yeni arayüze uyarlar
37 private final EskiOdemeApi eski;
38 EskiOdemeAdapter(EskiOdemeApi eski) { this.eski = eski; }
39 public void ode(double tutar, String paraBirimi) {
40 eski.makePayment((int) Math.round(tutar * 100), paraBirimi); // TL -> kuruş dönüşümü
41 }
42}
43
44// --- Decorator ---
45interface Kahve { String aciklama(); double fiyat(); }
46class SadeKahve implements Kahve {
47 public String aciklama() { return "Kahve"; }
48 public double fiyat() { return 30; }
49}
50abstract class KahveSusleyici implements Kahve { // ortak sarmalayıcı taban
51 protected final Kahve ic;
52 KahveSusleyici(Kahve ic) { this.ic = ic; }
53}
54class SutEkle extends KahveSusleyici {
55 SutEkle(Kahve ic) { super(ic); }
56 public String aciklama() { return ic.aciklama() + " + süt"; }
57 public double fiyat() { return ic.fiyat() + 8; }
58}
59class SekerEkle extends KahveSusleyici {
60 SekerEkle(Kahve ic) { super(ic); }
61 public String aciklama() { return ic.aciklama() + " + şeker"; }
62 public double fiyat() { return ic.fiyat() + 3; }
63}
64
65// --- Facade ---
66class StokServisi { boolean dus(String urun) { System.out.println(" [stok] " + urun + " stoktan düşüldü."); return true; } }
67class KargoServisi { void planla(String urun) { System.out.println(" [kargo] " + urun + " için kargo planlandı."); } }
68
69class SiparisFacade {
70 private final StokServisi stok = new StokServisi();
71 private final KargoServisi kargo = new KargoServisi();
72 void siparisVer(String urun, double tutar) {
73 // Karmaşık adımları tek çağrının ardına gizler.
74 if (stok.dus(urun)) {
75 new EskiOdemeAdapter(new EskiOdemeApi()).ode(tutar, "TRY");
76 kargo.planla(urun);
77 System.out.println(" Sipariş tamamlandı: " + urun);
78 }
79 }
80}Ornek3
çalıştırılabilir1// Ornek3: Davranışsal (behavioral) desenler — Strategy, Observer, Command.
2// Gerçek senaryo: kargo ücreti stratejisi, stok takibi (bildirim), geri alınabilir komutlar.
3// Çalıştırma: java Ornek3.java
4import java.util.ArrayList;
5import java.util.ArrayDeque;
6import java.util.Deque;
7import java.util.List;
8
9public class Ornek3 {
10
11 public static void main(String[] args) {
12 // --- STRATEGY: algoritmayı (kargo ücreti) çalışma zamanında değiştir ---
13 double sepet = 800;
14 System.out.println("Sepet: " + sepet + " TL");
15 System.out.println(" Standart : " + KargoUcreti.STANDART.hesapla(sepet) + " TL");
16 System.out.println(" Ekspres : " + KargoUcreti.EKSPRES.hesapla(sepet) + " TL");
17 // Strateji bir lambda da olabilir (functional interface):
18 KargoStratejisi kampanya = tutar -> tutar >= 500 ? 0 : 40;
19 System.out.println(" Kampanya (500+ ücretsiz): " + kampanya.hesapla(sepet) + " TL");
20
21 // --- OBSERVER: stok değişince ilgili aboneler otomatik haberdar olsun ---
22 System.out.println("\nObserver — stok takibi:");
23 StokTakip stok = new StokTakip("Klavye");
24 stok.aboneOl(haber -> System.out.println(" [müşteri] " + haber));
25 stok.aboneOl(haber -> System.out.println(" [analitik] kaydedildi: " + haber));
26 stok.stokGuncelle(0); // tükendi -> herkese haber
27 stok.stokGuncelle(15); // geldi -> herkese haber
28
29 // --- COMMAND: işlemleri nesneleştir; geri alma (undo) mümkün olsun ---
30 System.out.println("\nCommand — geri alınabilir metin düzenleyici:");
31 Editor editor = new Editor();
32 KomutYoneticisi yonetici = new KomutYoneticisi();
33 yonetici.calistir(new YazKomutu(editor, "Merhaba"));
34 yonetici.calistir(new YazKomutu(editor, " dünya"));
35 System.out.println(" Metin: '" + editor.metin() + "'");
36 yonetici.geriAl(); // son yazmayı geri al
37 System.out.println(" Geri al -> '" + editor.metin() + "'");
38 yonetici.geriAl();
39 System.out.println(" Geri al -> '" + editor.metin() + "'");
40 }
41}
42
43// --- Strategy ---
44@FunctionalInterface
45interface KargoStratejisi { double hesapla(double sepetTutari); }
46enum KargoUcreti implements KargoStratejisi {
47 STANDART { public double hesapla(double t) { return t >= 1000 ? 0 : 30; } },
48 EKSPRES { public double hesapla(double t) { return 60; } }
49}
50
51// --- Observer ---
52@FunctionalInterface
53interface StokAbonesi { void bildirimAl(String haber); }
54class StokTakip {
55 private final String urun;
56 private final List<StokAbonesi> aboneler = new ArrayList<>();
57 StokTakip(String urun) { this.urun = urun; }
58 void aboneOl(StokAbonesi a) { aboneler.add(a); }
59 void stokGuncelle(int adet) {
60 String haber = adet == 0 ? urun + " tükendi" : urun + " stoğa geldi (" + adet + " adet)";
61 aboneler.forEach(a -> a.bildirimAl(haber));
62 }
63}
64
65// --- Command ---
66interface Komut { void calistir(); void geriAl(); }
67class Editor {
68 private final StringBuilder sb = new StringBuilder();
69 void ekle(String s) { sb.append(s); }
70 void sil(int uzunluk) { sb.delete(sb.length() - uzunluk, sb.length()); }
71 String metin() { return sb.toString(); }
72}
73class YazKomutu implements Komut {
74 private final Editor editor;
75 private final String metin;
76 YazKomutu(Editor editor, String metin) { this.editor = editor; this.metin = metin; }
77 public void calistir() { editor.ekle(metin); }
78 public void geriAl() { editor.sil(metin.length()); } // yazılanı geri al
79}
80class KomutYoneticisi {
81 private final Deque<Komut> gecmis = new ArrayDeque<>();
82 void calistir(Komut k) { k.calistir(); gecmis.push(k); }
83 void geriAl() { if (!gecmis.isEmpty()) gecmis.pop().geriAl(); }
84}