Yükleniyor...

C# Tasarım Desenleri (Factory, Singleton, Repository, Observer vb.)

C#’ta Factory, Singleton, Repository ve Observer gibi tasarım desenlerini öğrenin. Esnek ve sürdürülebilir yazılım geliştirme örneklerle anlatılıyor.

Tasarım desenleri (Design Patterns), yazılım geliştirmede tekrar eden problemler için standart ve kanıtlanmış çözümler sunar. C# dilinde yaygın olarak kullanılan desenler arasında Factory, Singleton, Repository, Observer, Strategy, Adapter ve Decorator bulunur. Bu makalede bu desenlerin temel mantığı, kullanım alanları ve C# örnekleri incelenmektedir.


Factory Pattern

Factory deseni, nesne oluşturma işini merkezileştirir. Kodun her yerinde new kullanmak yerine nesne üretme sorumluluğu factory sınıfa devredilir.


public interface IProduct { void DoWork(); }

public class ProductA : IProduct
{
    public void DoWork() => Console.WriteLine("Ürün A");
}

public class ProductB : IProduct
{
    public void DoWork() => Console.WriteLine("Ürün B");
}

public static class ProductFactory
{
    public static IProduct Create(string type) => type switch
    {
        "A" => new ProductA(),
        "B" => new ProductB(),
        _ => throw new ArgumentException("Geçersiz tip")
    };
}

Singleton Pattern

Singleton deseni, bir sınıftan yalnızca tek bir nesne oluşturulmasını garanti eder. Genellikle loglama, cache veya yapılandırma nesnelerinde kullanılır.


public class Singleton
{
    private static Singleton? _instance;
    private static readonly object _lock = new();

    private Singleton() { }

    public static Singleton Instance
    {
        get
        {
            lock (_lock)
                return _instance ??= new Singleton();
        }
    }
}

Repository Pattern

Repository deseni, veri erişim katmanını soyutlar. Böylece iş mantığı katmanı veritabanı detaylarından bağımsız olur.


public class Product { public int Id; public string Name = ""; }

public interface IProductRepository
{
    void Add(Product p);
    Product? GetById(int id);
}

public class InMemoryProductRepository : IProductRepository
{
    private readonly List<Product> _list = new();
    public void Add(Product p) => _list.Add(p);
    public Product? GetById(int id) => _list.FirstOrDefault(x => x.Id == id);
}

Observer Pattern

Observer deseni, yayıncı–abone (publisher–subscriber) ilişkisini kurar. Bir nesnede değişiklik olduğunda aboneler bilgilendirilir. C#’ta event/delegate mekanizmasıyla uygulanır.


public class NewsAgency
{
    public event EventHandler<string>? NewsPublished;
    public void Publish(string news)
    {
        Console.WriteLine($"Haber: {news}");
        NewsPublished?.Invoke(this, news);
    }
}

Strategy Pattern

Strategy deseni, algoritmaları birbirinin yerine kullanılabilir şekilde kapsüller. Müşteri sınıfı hangi algoritmanın kullanılacağını bilmek zorunda değildir, strateji dışarıdan verilir.


public interface IPaymentStrategy { void Pay(decimal amount); }

public class CreditCardPayment : IPaymentStrategy
{
    public void Pay(decimal amount) => Console.WriteLine($"{amount} TL kredi kartı ile ödendi.");
}

public class PayPalPayment : IPaymentStrategy
{
    public void Pay(decimal amount) => Console.WriteLine($"{amount} TL PayPal ile ödendi.");
}

public class Checkout
{
    private readonly IPaymentStrategy _strategy;
    public Checkout(IPaymentStrategy strategy) => _strategy = strategy;
    public void ProcessOrder(decimal total) => _strategy.Pay(total);
}

Adapter Pattern

Adapter deseni, uyumsuz iki sınıfı birbirine uydurur. Eski bir kütüphane veya API’yi yeni sisteme entegre etmek için kullanılır.


// Eski sistem
public class LegacyPrinter { public void PrintText(string t) => Console.WriteLine(t); }

// Yeni arayüz
public interface IPrinter { void Print(string text); }

// Adapter
public class PrinterAdapter : IPrinter
{
    private readonly LegacyPrinter _legacy;
    public PrinterAdapter(LegacyPrinter legacy) => _legacy = legacy;
    public void Print(string text) => _legacy.PrintText(text);
}

Decorator Pattern

Decorator deseni, mevcut nesnelere dinamik olarak yeni davranışlar eklemeyi sağlar. Bu, kalıtım yerine bileşenleştirme (composition) yaklaşımıdır.


public interface IMessage { string GetText(); }

public class SimpleMessage : IMessage
{
    private readonly string _text;
    public SimpleMessage(string text) => _text = text;
    public string GetText() => _text;
}

// Decorator
public class EncryptedMessage : IMessage
{
    private readonly IMessage _inner;
    public EncryptedMessage(IMessage inner) => _inner = inner;
    public string GetText() => Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(_inner.GetText()));
}

Avantajları


TL;DR

  • Factory: Nesne oluşturmayı soyutlar.
  • Singleton: Tek örnek garantisi.
  • Repository: Veri erişimini soyutlar.
  • Observer: Değişiklikleri abonelere bildirir.
  • Strategy: Algoritmalar kolayca değiştirilebilir.
  • Adapter: Uyumsuz sınıfları birbirine uydurur.
  • Decorator: Dinamik olarak özellik ekler.

Örnek: Ürün Oluşturma, Listeleme, Sipariş Akışı ve Loglama

Aşağıdaki örnekte ürün üretimi (Factory), listeleme (Repository), sipariş oluşturma ve durum güncelleme (Observer), ödeme (Strategy), fiş yazdırma (Adapter) ve mesaj işleme (Decorator) bir arada gösterilmektedir. Lazy ile güvenli tekil yapılandırma, enum tabanlı factory, özel EventArgs, kültüre duyarlı para biçimlendirme kullanılmaktadır.


using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;

// =======================
//  DOMAIN (Küçük model)
// =======================

// Ürün türlerini string yerine enum ile ifade ederek hatayı azaltırız.
public enum ProductType { Book, Phone }

// Basit veri taşıyıcı (immutable) kayıt türü
public record Product(int Id, string Name, decimal Price);

// =======================
//  REPOSITORY (Depolama)
// =======================

public interface IProductRepository
{
    void Add(Product p);
    Product? GetById(int id);
    IEnumerable<Product> GetAll(); // Dışarıya IEnumerable veriyoruz; iç listeyi expose etmeyelim.
}

public class InMemoryProductRepository : IProductRepository
{
    private readonly List<Product> _list = new();

    public void Add(Product p) => _list.Add(p);

    public Product? GetById(int id) => _list.FirstOrDefault(x => x.Id == id);

    // Koruma amaçlı kopya döndürmek istersek: return _list.ToList();
    public IEnumerable<Product> GetAll() => _list;
}

// =======================
//  FACTORY (Ürün üretimi)
// =======================

public static class ProductFactory
{
    // Üretim tek bir merkezde: Yeni tür eklendiğinde switch'e eklemek yeterli.
    public static Product Create(ProductType type, int id)
        => type switch
        {
            ProductType.Book  => new Product(id, $"Kitap-{id}",  29.90m),
            ProductType.Phone => new Product(id, $"Telefon-{id}", 599.00m),
            _ => throw new ArgumentOutOfRangeException(nameof(type))
        };
}

// =======================
//  TEKİL AYAR (Singleton)
// =======================

// Lazy<T> ile thread-safe, yavaş yüklenen, sade Singleton.
public sealed class AppSettings
{
    private static readonly Lazy<AppSettings> _lazy = new(() => new AppSettings());
    public static AppSettings Instance => _lazy.Value;

    private AppSettings() { }

    public string StoreName { get; init; } = "Mağazam";
}

// =======================
//  OBSERVER (Olay Modeli)
// =======================

// Özel EventArgs: string yerine genişlemeye uygun bir yapı.
public sealed class OrderStatusChangedEventArgs : EventArgs
{
    public string Status { get; }
    public DateTime When { get; }
    public OrderStatusChangedEventArgs(string status, DateTime when)
    {
        Status = status;
        When = when;
    }
}

public class Order
{
    public event EventHandler<OrderStatusChangedEventArgs>? StatusChanged;

    public int Id { get; }
    public Product Product { get; }

    public Order(int id, Product product)
    {
        Id = id;
        Product = product;
    }

    public void UpdateStatus(string status)
    {
        Console.WriteLine($"Sipariş {Id} durumu: {status}");
        StatusChanged?.Invoke(this, new OrderStatusChangedEventArgs(status, DateTime.Now));
    }
}

// Olay dinleyicileri (aboneler)
public class EmailNotifier
{
    public void OnOrderStatusChanged(object? sender, OrderStatusChangedEventArgs e)
        => Console.WriteLine($"[E-posta] Müşteriye bildirim gönderildi: {e.Status} ({e.When:T})");
}

public class SmsNotifier
{
    public void OnOrderStatusChanged(object? sender, OrderStatusChangedEventArgs e)
        => Console.WriteLine($"[SMS] Müşteriye bildirim gönderildi: {e.Status} ({e.When:T})");
}

// =======================
//  STRATEGY (Ödeme)
// =======================

public interface IPaymentStrategy
{
    void Pay(decimal amount);
}

public class CreditCardPayment : IPaymentStrategy
{
    public void Pay(decimal amount) => Console.WriteLine($"{amount:0.00} TL kredi kartı ile ödendi.");
}

public class PayPalPayment : IPaymentStrategy
{
    public void Pay(decimal amount) => Console.WriteLine($"{amount:0.00} TL PayPal ile ödendi.");
}

// =======================
//  ADAPTER (Fiş Yazdırma)
// =======================

// Eski API
public class LegacyPrinter
{
    public void PrintText(string t) => Console.WriteLine("[LegacyPrinter] " + t);
}

// Yeni arayüz
public interface IPrinter
{
    void Print(string text);
}

// Uyarlayıcı: Yeni arayüzü eski API'ya bağlar.
public class PrinterAdapter : IPrinter
{
    private readonly LegacyPrinter _legacy;
    public PrinterAdapter(LegacyPrinter legacy) => _legacy = legacy;
    public void Print(string text) => _legacy.PrintText(text);
}

// =======================
//  DECORATOR (Mesaj)
// =======================

public interface IMessage
{
    string GetText();
}

public class SimpleMessage : IMessage
{
    private readonly string _text;
    public SimpleMessage(string text) => _text = text;
    public string GetText() => _text;
}

// Mesajı loglayan dekoratör
public class LoggedMessageDecorator : IMessage
{
    private readonly IMessage _inner;
    public LoggedMessageDecorator(IMessage inner) => _inner = inner;

    public string GetText()
    {
        var text = _inner.GetText();
        var preview = text.Length > 60 ? text.Substring(0, 60) + "..." : text;
        Console.WriteLine("[Log] Mesaj oluşturuldu (önizleme): " + preview);
        return text;
    }
}

// Mesajı Base64 ile "şifreleyen" basit dekoratör
public class EncryptedMessageDecorator : IMessage
{
    private readonly IMessage _inner;
    public EncryptedMessageDecorator(IMessage inner) => _inner = inner;

    public string GetText()
    {
        var raw = _inner.GetText();
        return Convert.ToBase64String(Encoding.UTF8.GetBytes(raw));
    }
}

// =======================
//  UYGULAMA (Demo Akışı)
// =======================

class Program
{
    static void Main()
    {
        // TR kültürü ile para birimi biçimlendirme
        var tr = new CultureInfo("tr-TR");

        Console.WriteLine($"Hoş geldiniz: {AppSettings.Instance.StoreName}");
        Console.WriteLine();

        // Repository + Factory ile ürün ekleme
        IProductRepository repo = new InMemoryProductRepository();
        var p1 = ProductFactory.Create(ProductType.Book,  1);
        var p2 = ProductFactory.Create(ProductType.Phone, 2);
        repo.Add(p1);
        repo.Add(p2);

        // Ürün listeleme (kültüre duyarlı fiyat)
        Console.WriteLine("Depodaki ürünler:");
        foreach (var p in repo.GetAll())
            Console.WriteLine($"- {p.Id}: {p.Name} {p.Price.ToString("C", tr)}");
        Console.WriteLine();

        // Sipariş oluşturma ve olay abonelikleri
        var order = new Order(1001, p1);
        var email = new EmailNotifier();
        var sms   = new SmsNotifier();

        // Olaylara dinleyici ekleme
        order.StatusChanged += email.OnOrderStatusChanged;
        order.StatusChanged += sms.OnOrderStatusChanged;

        // Ödeme (Strategy)
        IPaymentStrategy payment = new CreditCardPayment();
        Console.WriteLine("Ödeme işleniyor:");
        payment.Pay(order.Product.Price);
        Console.WriteLine();

        // Sipariş yaşam döngüsü (Observer tetiklenir)
        order.UpdateStatus("Onaylandı");
        order.UpdateStatus("Kargolandı");

        // (İsteğe bağlı) SMS aboneliğini kaldırma örneği
        order.StatusChanged -= sms.OnOrderStatusChanged;
        order.UpdateStatus("Teslim Edildi");
        Console.WriteLine();

        // Fiş yazdırma (Adapter ile eski yazıcı API'sini kullan)
        IPrinter printer = new PrinterAdapter(new LegacyPrinter());
        printer.Print($"Sipariş #{order.Id} fişi: {order.Product.Name} - {order.Product.Price.ToString("C", tr)}");
        Console.WriteLine();

        // Mesaj işleme (Decorator)
        // Amaç: Düz metni logla, ardından saklamak/göndermek üzere şifrele.
        // Bu nedenle önce Logged, sonra Encrypted dekoratörünü zincirliyoruz.
        IMessage msg = new SimpleMessage($"Satın aldığınız için teşekkürler: {order.Product.Name}!");
        msg = new LoggedMessageDecorator(msg);      // 1) Log: DÜZ METİN loglansın
        msg = new EncryptedMessageDecorator(msg);   // 2) Şifrele: Dışarıya şifreli metin gitsin

        var finalText = msg.GetText();
        Console.WriteLine("Sistemde saklanacak/gönderilecek son mesaj (Base64):");
        Console.WriteLine(finalText);
        Console.WriteLine();

        // Çalışma anında strateji değiştirme
        payment = new PayPalPayment();
        Console.WriteLine("Farklı bir ödeme stratejisi ile ikinci ödeme:");
        payment.Pay(199.99m);

        Console.WriteLine("\nDemo tamamlandı.");
    }
}

İlişkili Makaleler

C# ile SOLID Prensipleri

C# örnekleriyle SOLID prensiplerinin uygulanışı: daha esnek, sürdürülebilir ve test edilebilir kod tasarımları.