Bir web uygulamasında her istek önce kimlik doğrulamadan, ardından yetkilendirmeden ve son olarak veri doğrulamadan geçer. Ancak her adımı kontrol eden kod if-else bloklarına gömüldüğünde esneklik azalır ve bakım zorlaşır. İşte bu noktada, “Chain of Responsibility” (Sorumluluk Zinciri) deseni devreye girer. Bu desen, isteği bir dizi işleyici (handler) üzerinden geçirir. Her işleyici isteği kontrol eder; eğer işlem yapabiliyorsa gerçekleştirir, değilse sıradakine devreder. Böylece gönderici ile alıcı gevşek bağlanır. Ayrıca, zincir çalışma zamanında kolayca değiştirilebilir.

chain_of_responsibility

Neden ilgilenmeli? Çünkü bu desen, kapalı-uzatılabilir yapı kurmaya, çapraz-kesim kontrolleri (auth, logging, rate limit) sadeleştirmeye ve ASP.NET Core gibi modern mimarilerdeki middleware zincirini daha iyi anlamaya yardımcı olur.

Temel Kavramlar ve Prensipler

  • Tanım: İstekler, bir dizi işleyici boyunca dolaşır; her işleyici ya isteği işler ya da bir sonrakine iletir.
  • Amaç: Göndericiyi somut alıcılardan ayırmak, birden fazla nesneye isteği ele alma fırsatı vermek.
  • Bileşenler:
    • Handler arayüzü/soyut sınıf: İsteği işleme ve bir sonraki işleyiciyi bağlama sözleşmesi.

    • Concrete Handler’lar: Kendi sorumluluğunu üstlenir, gerekirse devreder.

    • Client: Zincirin ilk halkasına isteği gönderir; kimin işleyeceğini bilmez.

  • Özellikler:

    • Gevşek bağ, dinamik zincir, tek sorumluluk, sıralı işleme, opsiyonel “fallback”.

Kısa özet: Zincir, “sorumluluğu küçük parçalara böl, sırayla dene, uygun olan işlesin” prensibiyle daha temiz ve esnek bir akış sağlar.

Modelin Derin Analizi

Desenin özünde, standart bir “Handler” sözleşmesi ve zinciri kurmayı kolaylaştıran “SetNext” yaklaşımı vardır. Her işleyici Handle(request) içinde “işleyebilir miyim?” kararını verir; işleyemiyorsa zincirdeki bir sonraki halkaya iletir. Bu yapı koşullu dallanmaları merkezsizleştirir ve yeni kurallar eklemeyi “yeni bir handler ekle, zincire bağla” seviyesine indirir.

chain_of_responsibility

C# konsept akışı:

  • IHandler: SetNext(IHandler) ve Handle(Request) tanımlar.
  • HandlerBase: Ortak SetNext ve isteği devretme mantığını içerir.
  • Concrete’ler: Validasyon, yetki, caching, rate limit gibi tek bir sorumluluğu uygular.
  • İstemci: İlk halkaya isteği yollar; geri kalan zincir karar verir.

Refactoring.Guru ve Code Maze, C#’ta zincirin nasıl kurulacağını ve basit bir HandlerBase ile bağlamanın nasıl sadeleştirileceğini gösterir.

C# ile Kısa ve Net Örnek

Aşağıdaki minimal iskelet, zinciri kurup çalıştırmayı gösterir (HandlerBase ile bağlamayı sadeleştirir):

public interface IHandler<TRequest, TResponse>
{
    IHandler<TRequest, TResponse> SetNext(IHandler<TRequest, TResponse> next);
    TResponse Handle(TRequest request);
}

public abstract class HandlerBase<TRequest, TResponse> : IHandler<TRequest, TResponse>
{
    private IHandler<TRequest, TResponse>? _next;
    public IHandler<TRequest, TResponse> SetNext(IHandler<TRequest, TResponse> next)
    {
        _next = next;
        return next; // zincirlemeyi kolaylaştırır
    }
    public virtual TResponse Handle(TRequest request)
    {
        if (_next is null) throw new InvalidOperationException("No handler processed the request.");
        return _next.Handle(request);
    }
}

// Örnek senaryo: doğrulama -> yetki -> işlem
public record OrderRequest(string UserRole, decimal Amount, bool IsValid);
public record OrderResponse(bool Success, string Message);

public class ValidationHandler : HandlerBase<OrderRequest, OrderResponse>
{
    public override OrderResponse Handle(OrderRequest request)
        => request.IsValid
            ? base.Handle(request)
            : new OrderResponse(false, "Validation failed");
}

public class AuthorizationHandler : HandlerBase<OrderRequest, OrderResponse>
{
    public override OrderResponse Handle(OrderRequest request)
        => request.UserRole == "Admin"
            ? base.Handle(request)
            : new OrderResponse(false, "Unauthorized");
}

public class ProcessOrderHandler : HandlerBase<OrderRequest, OrderResponse>
{
    public override OrderResponse Handle(OrderRequest request)
        => new OrderResponse(true, $"Processed amount: {request.Amount}");
}

// Kurulum
var validation = new ValidationHandler();
var auth = new AuthorizationHandler();
var process = new ProcessOrderHandler();
validation.SetNext(auth).SetNext(process);

// Kullanım
var response = validation.Handle(new OrderRequest("Admin", 150m, true));

Pratik Uygulamalar ve İpuçları

Hızlı kullanım alanları:

  • HTTP pipeline/middleware (ASP.NET Core): Doğal bir Chain of Responsibility örneği.
  • Form/DTO doğrulama: Alan bazlı kurallar ardışık işlenmelidir.
  • Onay/escalation akışları: Yönetici–Direktör–CEO onay hiyerarşisi.
  • Destek talepleri yönlendirme: Seviye 1→2→3 destek akışı.
  • ATM para dağıtımı, filtreleme, event işleme: Sıralı karar mantıkları.

Best practices:

  • Handler’ları tek sorumlulukla küçük tut.
  • Handler zincirini kompozisyonla kur; sıralama iş kuralına göre belirgin olsun.
  • Boşta kalma/fallback için son bir “DefaultHandler” ekle.
  • Yan etkileri sınırlayıp bağımlılıkları constructor ile enjekte et.
  • Zinciri çalışma zamanında konfigürasyonla değiştirilebilirsin.

Sık hatalar:

  • God Handler: Bir handler’ın çok iş yapması.
  • Gizli bağımlılıklar: Handler’ların birbirini bilmesi; zinciri kırar.
  • Döngüsel zincir: Sonraki referanslarla farkında olmadan döngü yaratmak.
  • Hata yönetimi eksikliği: Hiçbir handler’ın işlememesi durumunda belirsizlik.

Gelişmiş Bakış

  • Middleware mimarileri: ASP.NET Core’daki Use/Run/Map ile oluşturulan pipeline, CoR’nin üretim sınıfı örneğidir; her middleware ya işler ya da bir sonrakine delege eder.
  • Dinamik konfigürasyon: Özellik bayrakları veya policy provider ile zinciri canlıda farklılaştırmak mümkündür.
  • Performans: Uzun zincirlerde kısa devre(early exit), önceliklendirme ve öncül filtreler ile maliyeti azalt.
  • Test edilebilirlik: Her handler bağımsız test edilir, zincir entegrasyon testleriyle doğrulanır.
  • Alternatifler: Strategy (tek seçime göre davranış), Decorator (sarmalama), Interceptor/AOP (çapraz-kesim), Observer (yayınla-abone ol) — ihtiyaca göre CoR ile birlikte veya yerine düşünülebilir.
  • Kurumsal örnek: Satın alma onay akışında Director→VP→President zinciri, klasik CoR vitrini olarak bilinir.

SSS – Sık Sorulan Sorular

  • Observer ile farkı nedir?
    Observer, olayı birden çok aboneye yayınlar. Buna karşılık, CoR tek bir isteği sırayla dolaştırır ve uygun halkada durur.
  • Neden if-else yerine CoR?
    CoR, kuralları modülerleştirir. Geliştirici ekleme, çıkarma veya sıralama gibi değişiklikleri sınıf seviyesinde yapabilir. Bu nedenle, gönderici alıcıdan ayrılır ve esneklik artar.
  • Her zaman zincir gerekir mi?
    Basit tek karar noktalarında Strategy pattern daha uygundur ya da doğrudan metod çağrısı kullanabilirsin. Öte yandan, CoR sıralı, çok adımlı ve koşullu işlemlerde daha iyi sonuç verir.

Hızlı İpuçları Kutusu

  • Handler’ları küçük tut, isimleri sorumluluğu anlatsın.
  • Zincire son bir “Default/Null Handler” ekleyerek hatasız kapanış sağla.
  • Performans için erken dönüş ve öncelik sıralaması uygula.
  • Testlerde her handler’ı izole et, zinciri entegrasyon testleriyle doğrula.

Sonuç

Chain of Responsibility, koşullu dallanmaları modüler işleyicilere bölerek daha okunabilir, esnek ve test edilebilir akışlar kurmayı sağlar. Middleware’den onay akışına kadar pek çok gerçek senaryoda zahmetsiz ölçeklenebilirlik kazandırır.

Kaynak notları:

By tanju.bozok

Software Architect, Developer, and Entrepreneur

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir