Yazılım Geliştirme

Design Patterns: En Çok Kullanılan 5 Tasarım Kalıbı

Fatih Algül
24.03.2026 372 görüntülenme

Tasarım Kalıpları Neden Önemlidir?

Yazılım geliştirme sürecinde karşılaşılan tekrarlayan problemlere kanıtlanmış çözümler sunan tasarım kalıpları (design patterns), her geliştiricinin araç kutusunda bulunması gereken temel yapı taşlarıdır. Gang of Four (GoF) tarafından 1994 yılında sistematize edilen bu kalıplar, kodunuzu daha okunabilir, sürdürülebilir ve esnek hale getirir. Bu yazıda, pratikte en sık karşılaşacağınız ve kullanacağınız 5 tasarım kalıbını detaylı olarak inceleyeceğiz.

1. Singleton (Tek Nesne) Kalıbı

Ne Zaman Kullanılır?

Bir sınıftan yalnızca tek bir örnek (instance) oluşturulmasını ve bu örneğe global bir erişim noktası sağlanmasını istediğinizde Singleton kalıbını kullanırsınız. Veritabanı bağlantıları, yapılandırma yöneticileri ve loglama servisleri bu kalıbın en yaygın kullanım alanlarıdır.

Nasıl Çalışır?

Sınıfın constructor'ı private yapılarak dışarıdan doğrudan nesne oluşturulması engellenir. Bunun yerine, statik bir metot aracılığıyla nesneye erişilir. İlk çağrıda nesne oluşturulur, sonraki çağrılarda aynı nesne döndürülür.

public class DatabaseConnection {
    private static DatabaseConnection instance;

    private DatabaseConnection() {
        // Bağlantı kurulumu
    }

    public static synchronized DatabaseConnection getInstance() {
        if (instance == null) {
            instance = new DatabaseConnection();
        }
        return instance;
    }

    public void query(String sql) {
        System.out.println("Sorgu çalıştırılıyor: " + sql);
    }
}

Dikkat Edilmesi Gerekenler

  • Multi-thread ortamlarda thread safety sağlamak için synchronized anahtar kelimesi veya double-checked locking kullanılmalıdır.
  • Aşırı kullanımı global state oluşturarak test edilebilirliği düşürür. Dependency Injection ile birlikte kullanmak daha sağlıklıdır.
  • Singleton, aslında kontrollü bir global değişkendir; gerçekten tek bir instance gerekip gerekmediğini sorgulamak önemlidir.

2. Observer (Gözlemci) Kalıbı

Ne Zaman Kullanılır?

Bir nesnedeki değişikliklerin, ona bağımlı olan diğer nesnelere otomatik olarak bildirilmesi gerektiğinde Observer kalıbını kullanırsınız. Event sistemleri, UI framework'leri (React'in state yönetimi gibi) ve bildirim mekanizmaları bu kalıbın tipik uygulama alanlarıdır.

Nasıl Çalışır?

Bir Subject (yayıncı) nesnesi, kendisine abone olan Observer (gözlemci) nesnelerinin listesini tutar. Subject'in durumu değiştiğinde, tüm observer'lara bildirim gönderilir.

import java.util.ArrayList;
import java.util.List;

interface Observer {
    void update(String event, Object data);
}

class EventManager {
    private List<Observer> listeners = new ArrayList<>();

    public void subscribe(Observer listener) {
        listeners.add(listener);
    }

    public void unsubscribe(Observer listener) {
        listeners.remove(listener);
    }

    public void notify(String event, Object data) {
        for (Observer listener : listeners) {
            listener.update(event, data);
        }
    }
}

class StockPriceAlert implements Observer {
    @Override
    public void update(String event, Object data) {
        System.out.println("Fiyat değişikliği: " + event + " -> " + data);
    }
}

Pratik İpuçları

  • Observer'ları kaldırmayı (unsubscribe) unutmak memory leak'lere yol açabilir.
  • Çok fazla observer zinciri oluşturmak performans sorunlarına neden olabilir; bu durumda bir event bus mimarisi düşünülebilir.
  • JavaScript dünyasında bu kalıp addEventListener API'si ile doğal olarak desteklenmektedir.

3. Factory Method (Fabrika Metodu) Kalıbı

Ne Zaman Kullanılır?

Nesne oluşturma mantığını istemci kodundan soyutlamak istediğinizde Factory kalıbını kullanırsınız. Hangi sınıftan nesne oluşturulacağına çalışma zamanında (runtime) karar verilmesi gereken durumlarda idealdir. Ödeme sistemleri, dosya formatı dönüştürücüleri ve bildirim servisleri gibi senaryolarda sıklıkla karşılaşılır.

Nasıl Çalışır?

Bir fabrika sınıfı veya metodu, gelen parametreye göre uygun alt sınıfın nesnesini oluşturur ve döndürür. İstemci kodu somut sınıfları bilmek zorunda kalmaz.

interface Notification {
    void send(String message);
}

class EmailNotification implements Notification {
    public void send(String message) {
        System.out.println("E-posta gönderiliyor: " + message);
    }
}

class SmsNotification implements Notification {
    public void send(String message) {
        System.out.println("SMS gönderiliyor: " + message);
    }
}

class PushNotification implements Notification {
    public void send(String message) {
        System.out.println("Push bildirim gönderiliyor: " + message);
    }
}

class NotificationFactory {
    public static Notification create(String type) {
        return switch (type.toLowerCase()) {
            case "email" -> new EmailNotification();
            case "sms"   -> new SmsNotification();
            case "push"  -> new PushNotification();
            default -> throw new IllegalArgumentException("Bilinmeyen bildirim tipi: " + type);
        };
    }
}

// Kullanım
Notification n = NotificationFactory.create("email");
n.send("Siparişiniz kargoya verildi.");

Avantajları

  • Open/Closed Principle: Yeni tipler eklemek için mevcut kodu değiştirmek yerine yeni sınıflar eklemek yeterlidir.
  • İstemci kodu somut implementasyonlardan bağımsız hale gelir.
  • Nesne oluşturma mantığı tek bir yerde toplandığı için bakım kolaylaşır.

4. Strategy (Strateji) Kalıbı

Ne Zaman Kullanılır?

Bir işlemi gerçekleştirmek için birden fazla algoritma veya davranış seçeneğiniz olduğunda ve bunlar arasında çalışma zamanında geçiş yapmanız gerektiğinde Strategy kalıbını kullanırsınız. Sıralama algoritmaları, ödeme yöntemleri, fiyatlandırma kuralları ve dosya sıkıştırma stratejileri bu kalıbın klasik kullanım alanlarıdır.

Nasıl Çalışır?

Her algoritma kendi sınıfında kapsüllenir ve ortak bir arayüzü (interface) uygular. Ana sınıf (context), hangi stratejinin kullanılacağını bilmeden strateji nesnesine iş delege eder.

interface PricingStrategy {
    double calculatePrice(double basePrice);
}

class RegularPricing implements PricingStrategy {
    public double calculatePrice(double basePrice) {
        return basePrice;
    }
}

class PremiumMemberPricing implements PricingStrategy {
    public double calculatePrice(double basePrice) {
        return basePrice * 0.8; // %20 indirim
    }
}

class BlackFridayPricing implements PricingStrategy {
    public double calculatePrice(double basePrice) {
        return basePrice * 0.5; // %50 indirim
    }
}

class ShoppingCart {
    private PricingStrategy strategy;

    public void setPricingStrategy(PricingStrategy strategy) {
        this.strategy = strategy;
    }

    public double checkout(double basePrice) {
        return strategy.calculatePrice(basePrice);
    }
}

// Kullanım
ShoppingCart cart = new ShoppingCart();
cart.setPricingStrategy(new BlackFridayPricing());
double finalPrice = cart.checkout(200.0); // 100.0

Neden if-else Yerine Strategy?

Birçok geliştirici bu tür davranış farklılıklarını if-else veya switch blokları ile çözer. Ancak koşul sayısı arttıkça kod karmaşıklaşır ve bakımı zorlaşır. Strategy kalıbı ile her davranış kendi sınıfına taşınır, Single Responsibility Principle korunur ve yeni stratejiler eklemek mevcut koda dokunmadan mümkün olur.

5. Decorator (Dekoratör) Kalıbı

Ne Zaman Kullanılır?

Bir nesneye çalışma zamanında dinamik olarak yeni sorumluluklar eklemek istediğinizde Decorator kalıbını kullanırsınız. Alt sınıflama (inheritance) ile sorumluluk eklemenin pratik olmadığı veya kombinasyon sayısının çok fazla olduğu durumlarda idealdir. Java'nın I/O stream'leri (BufferedReader, InputStreamReader) bu kalıbın en bilinen örneğidir.

Nasıl Çalışır?

Decorator sınıfı, dekore ettiği nesne ile aynı arayüzü uygular ve orijinal nesneyi sarmalar (wrap). Kendi davranışını ekledikten sonra çağrıyı sarmaladığı nesneye delege eder.

interface DataSource {
    String read();
    void write(String data);
}

class FileDataSource implements DataSource {
    private String data = "";

    public String read() { return data; }
    public void write(String data) { this.data = data; }
}

class EncryptionDecorator implements DataSource {
    private DataSource wrapped;

    public EncryptionDecorator(DataSource source) {
        this.wrapped = source;
    }

    public String read() {
        return decrypt(wrapped.read());
    }

    public void write(String data) {
        wrapped.write(encrypt(data));
    }

    private String encrypt(String data) { return "ENC[" + data + "]"; }
    private String decrypt(String data) { return data.replace("ENC[", "").replace("]", ""); }
}

class CompressionDecorator implements DataSource {
    private DataSource wrapped;

    public CompressionDecorator(DataSource source) {
        this.wrapped = source;
    }

    public String read() {
        return decompress(wrapped.read());
    }

    public void write(String data) {
        wrapped.write(compress(data));
    }

    private String compress(String data) { return "ZIP(" + data + ")"; }
    private String decompress(String data) { return data.replace("ZIP(", "").replace(")", ""); }
}

// Kullanım: Katmanlar iç içe eklenir
DataSource source = new CompressionDecorator(
    new EncryptionDecorator(
        new FileDataSource()
    )
);
source.write("gizli veri");

Decorator vs Inheritance

  • Inheritance ile her kombinasyon için ayrı bir alt sınıf gerekir (EncryptedCompressedFile, CompressedFile, EncryptedFile...). Kombinasyon sayısı arttıkça sınıf patlaması yaşanır.
  • Decorator ile davranışlar çalışma zamanında özgürce birleştirilebilir ve sıralanabilir.
  • Python'da @decorator sözdizimi, bu kalıbın dile entegre edilmiş bir versiyonudur.

Hangi Kalıbı Ne Zaman Seçmelisiniz?

Doğru tasarım kalıbını seçmek, kalıbı uygulamak kadar önemlidir. İşte hızlı bir karar rehberi:

  1. Singleton: Uygulama genelinde tek bir paylaşımlı kaynak gerektiğinde (config, cache, connection pool).
  2. Observer: Bir nesnedeki değişikliğin birden fazla bileşeni tetiklemesi gerektiğinde (event-driven mimari).
  3. Factory: Nesne oluşturma mantığının karmaşıklaştığı veya koşullu hale geldiği durumlarda.
  4. Strategy: Aynı işi farklı yollarla yapmanız ve bunlar arasında geçiş yapmanız gerektiğinde.
  5. Decorator: Mevcut nesnelere alt sınıflamadan yeni davranışlar eklemek istediğinizde.

Sonuç

Tasarım kalıpları, yazılım geliştirmenin ortak dilidir. Bir ekip arkadaşınıza "burada Strategy pattern kullanıyoruz" dediğinizde, satırlarca açıklama yapmadan mimariyi anlatmış olursunuz. Ancak en önemli kural şudur: kalıpları problem için kullanın, kalıp kullanmak için problem yaratmayın. Her tasarım kalıbı bir miktar karmaşıklık ekler; bu karmaşıklığın getirdiği esneklik gerçekten gerekli olmadığında, basit ve doğrudan bir çözüm her zaman daha iyidir. Önce problemi anlayın, sonra doğru kalıbı uygulayın.

Yazar Hakkında
Fatih Algül
TechSoft Solutions
Proje mi var?

Yazılım, IoT veya otomasyon konularında destek almak ister misiniz?

İletişime Geç