Chain of Responsibility Deseni Nedir?

Sayfayı kopyala
Bu yazı Design Patterns/Tasarım Desenleri Nedir? başlıklı yazı dizisinin bir parçasıdır.
Bu içerik ağırlıklı olarak refactoring.guru sitesindeki içeriğin tercümesi ve derlenmesinden oluşturulmuştur.
Tüm tasarım desenleri ya da diğer adıyla tasarım kalıplarına yönelik ayrıntılı içeriklere yazının sonundaki bağlantılardan ulaşabilirsiniz.
💡 Özet (TL;DR):
- Amaç: Bir isteği, işleyicilerden oluşan bir zincir (chain) boyunca iletmek. Her işleyici isteği işleyebilir ya da bir sonraki işleyiciye aktarabilir.
- Kilit Yapılar: İşleyici Arayüzü (
Handler), Temel/Soyut İşleyici (AbstractHandler) ve Somut İşleyiciler (ConcreteHandlers).- Motto: Pass or Process (Aktar veya İşle). İsteği gönderen ile karşılayan sınıfları birbirinden tamamen ayırır.
Chain of Responsibility Deseninin Amacı
Chain of Responsibility (Sorumluluk Zinciri), bir isteği bir dizi işleyici (zinciri) boyunca iletmenize izin veren davranışsal (behavioral) bir tasarım desenidir. Özellikle web geliştirmedeki Middleware (Ara Yazılım) mimarilerinin temelini oluşturur.
Sorun
Çevrim içi sipariş alan bir e-ticaret sistemi üzerinde çalıştığınızı hayal edin. Sisteme girişlerin sınırlı olmasını, sadece doğrulanmış kullanıcıların sipariş verebilmesini istiyorsunuz. Ayrıca yönetici (admin) rolündeki kullanıcıların siparişlere tam erişim yetkisi olmalı.
Bu kontrollerin sırayla yapılması gerekir. İstek geldiğinde önce kullanıcı doğrulanmalı, başarısızsa işlem hemen kesilmelidir.

Zamanla sisteme yeni sıralı kontroller eklemeniz istenir:
- Siparişe giden ham verilerin temizlenmesi (Sanitization).
- Aynı IP adresinden gelen brute-force şifre kırma denemelerinin filtrelenmesi.
- Önbellekte (Cache) sonuç varsa veritabanına gitmeden isteğin hemen yanıtlanması.

Tüm bu kontrol mantığını tek bir büyük sınıfa yazdığınızda kod şişer, okunması ve bakımı zorlaşır. Bir kontroldeki değişiklik diğerlerini etkiler. Daha da kötüsü, bu kontrollerin bazılarını sistemin başka yerlerinde tekrar kullanmak istediğinizde kod kopyalamak zorunda kalırsınız.
Çözüm
Sorumluluk Zinciri deseni, her bir kontrol adımını bağımsız ve tek sorumluluğa sahip işleyici (handler) sınıflarına dönüştürmeyi önerir. Her işleyici, zincirdeki bir sonraki işleyicinin referansını tutar.
İstek zincirin başındaki ilk işleyiciye gönderilir. Her işleyici isteği işler ve işleme bittikten sonra isteği zincirdeki bir sonrakine aktarıp aktarmayacağına karar verir.
Örneğin, kullanıcı girişi hatalıysa istek sonraki filtreleme veya önbellek adımlarına hiç aktarılmadan süreç hemen sonlandırılır.

Tüm işleyicilerin aynı ortak arayüzü (interface) uygulaması şarttır. Bu sayede istemci kod, işleyicilerin somut sınıflarından bağımsız olarak çalışma zamanında (runtime) zincirin sırasını değiştirebilir veya araya yeni halkalar ekleyebilir.
Gerçek Hayat Senaryosu: Destek Masası (Support Ticket Escalation)
Müşteri destek biletlerinin önem derecesine ve teknik zorluğuna göre sırayla yönlendirilmesi süreci:
// 1. Ortak Arayüz
interface HelpDeskHandler {
public function setNext(HelpDeskHandler $handler): HelpDeskHandler;
public function handle(string $ticketSeverity): void;
}
// 2. Temel Sınıf
abstract class AbstractHelpDeskHandler implements HelpDeskHandler {
private ?HelpDeskHandler $nextHandler = null;
public function setNext(HelpDeskHandler $handler): HelpDeskHandler {
$this->nextHandler = $handler;
return $handler;
}
public function handle(string $ticketSeverity): void {
if ($this->nextHandler) {
$this->nextHandler->handle($ticketSeverity);
}
}
}
// 3. Somut İşleyiciler
class BotHandler extends AbstractHelpDeskHandler {
public function handle(string $ticketSeverity): void {
if ($ticketSeverity === "low") {
echo "Bot: Sık sorulan sorular yardımıyla talep çözüldü.\n";
} else {
echo "Bot: Talep karmaşık, teknik ekibe aktarılıyor...\n";
parent::handle($ticketSeverity);
}
}
}
class TechSupportHandler extends AbstractHelpDeskHandler {
public function handle(string $ticketSeverity): void {
if ($ticketSeverity === "medium") {
echo "Teknik Destek: Hata incelendi ve çözüldü.\n";
} else {
echo "Teknik Destek: Talep kritik düzeyde, Sistem Yöneticisine aktarılıyor...\n";
parent::handle($ticketSeverity);
}
}
}
class SysAdminHandler extends AbstractHelpDeskHandler {
public function handle(string $ticketSeverity): void {
if ($ticketSeverity === "high") {
echo "Sistem Yöneticisi: Sunucu bazlı kritik hata çözüldü!\n";
} else {
parent::handle($ticketSeverity);
}
}
}
Chain of Responsibility vs Decorator vs Command
| Desen | Amaç | İstek Akışını Kesme Yeteneği |
|---|---|---|
| Chain of Responsibility | İsteği potansiyel işleyiciler zinciri boyunca iletmek. | Evet, herhangi bir halkada akış durdurulabilir. |
| Decorator | Arayüzü bozmadan nesneye dinamik özellikler eklemek. | Hayır, sarmalanan tüm decorator'lar sırayla çalışır. |
| Command | Gönderici ve alıcıyı ayırıp işlemi nesneye dönüştürmek. | Uygulanamaz, doğrudan tek bir alıcıyı hedefler. |
Uygulanabilirlik
- Bilinmeyen İşleyici Sırası: Programın farklı türdeki istekleri çeşitli şekillerde işlemesi gerektiğinde ancak isteklerin türleri ve sıralamaları önceden bilinmediğinde kullanın.
- Sıralı Yürütme Gereksinimi: Belirli kontrollerin veya işlemlerin tam olarak planlanan sırada yürütülmesi gerektiğinde kullanın.
- Dinamik Zincir Değişikliği: Zincirdeki işleyicilerin ve sıralarının çalışma zamanında (runtime) dinamik olarak değişmesi gerektiğinde kullanın.
Diğer Tasarım Desenleri ile İlişkisi
- Chain of Responsibility sıklıkla Composite deseniyle birlikte kullanılır. Bir yaprak bileşen istek aldığında, onu tüm ana bileşenlerin zincirinden geçirerek nesne ağacının köküne kadar iletebilir.
- Zincirdeki işleyiciler birer Command olarak uygulanabilir. Bu sayede istek nesnesi üzerinde farklı işlemler çalıştırılabilir.
- Decorator ve Chain of Responsibility benzer sınıf yapılarına sahiptir ancak Decorator nesnenin davranışını genişletirken akışı kesemez, Chain of Responsibility ise akışı tamamen durdurabilir.
Chain of Responsibility Tasarım Deseni Kod Örnekleri
Örnek PHP Kodu
<?php
namespace RefactoringGuru\ChainOfResponsibility\Conceptual;
interface Handler
{
public function setNext(Handler $handler): Handler;
public function handle(string $request): ?string;
}
abstract class AbstractHandler implements Handler
{
private ?Handler $nextHandler = null;
public function setNext(Handler $handler): Handler
{
$this->nextHandler = $handler;
return $handler;
}
public function handle(string $request): ?string
{
if ($this->nextHandler) {
return $this->nextHandler->handle($request);
}
return null;
}
}
class MonkeyHandler extends AbstractHandler
{
public function handle(string $request): ?string
{
if ($request === "Banana") {
return "Monkey: I'll eat the " . $request . ".\n";
}
return parent::handle($request);
}
}
class SquirrelHandler extends AbstractHandler
{
public function handle(string $request): ?string
{
if ($request === "Nut") {
return "Squirrel: I'll eat the " . $request . ".\n";
}
return parent::handle($request);
}
}
class DogHandler extends AbstractHandler
{
public function handle(string $request): ?string
{
if ($request === "MeatBall") {
return "Dog: I'll eat the " . $request . ".\n";
}
return parent::handle($request);
}
}
function clientCode(Handler $handler)
{
foreach (["Nut", "Banana", "Cup of coffee"] as $food) {
echo "Client: Who wants a " . $food . "?\n";
$result = $handler->handle($food);
if ($result) {
echo " " . $result;
} else {
echo " " . $food . " was left untouched.\n";
}
}
}
$monkey = new MonkeyHandler();
$squirrel = new SquirrelHandler();
$dog = new DogHandler();
$monkey->setNext($squirrel)->setNext($dog);
echo "Chain: Monkey > Squirrel > Dog\n\n";
clientCode($monkey);
echo "\n";
echo "Subchain: Squirrel > Dog\n\n";
clientCode($squirrel);
Örnek Python Kodu
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Any, Optional
class Handler(ABC):
@abstractmethod
def set_next(self, handler: Handler) -> Handler:
pass
@abstractmethod
def handle(self, request: Any) -> Optional[str]:
pass
class AbstractHandler(Handler):
_next_handler: Handler = None
def set_next(self, handler: Handler) -> Handler:
self._next_handler = handler
return handler
def handle(self, request: Any) -> Optional[str]:
if self._next_handler:
return self._next_handler.handle(request)
return None
class MonkeyHandler(AbstractHandler):
def handle(self, request: Any) -> Optional[str]:
if request == "Banana":
return f"Monkey: I'll eat the {request}"
return super().handle(request)
class SquirrelHandler(AbstractHandler):
def handle(self, request: Any) -> Optional[str]:
if request == "Nut":
return f"Squirrel: I'll eat the {request}"
return super().handle(request)
class DogHandler(AbstractHandler):
def handle(self, request: Any) -> Optional[str]:
if request == "MeatBall":
return f"Dog: I'll eat the {request}"
return super().handle(request)
def client_code(handler: Handler) -> None:
for food in ["Nut", "Banana", "Cup of coffee"]:
print(f"\nClient: Who wants a {food}?")
result = handler.handle(food)
if result:
print(f" {result}", end="")
else:
print(f" {food} was left untouched.", end="")
if __name__ == "__main__":
monkey = MonkeyHandler()
squirrel = SquirrelHandler()
dog = DogHandler()
monkey.set_next(squirrel).set_next(dog)
print("Chain: Monkey > Squirrel > Dog")
client_code(monkey)
print("\n")
print("Subchain: Squirrel > Dog")
client_code(squirrel)
Sıkça Sorulan Sorular (FAQ)
Zincirin sonuna ulaşan ve hiçbir işleyici tarafından işlenmeyen istekler için ne yapılmalıdır?
İstek zincirin son halkasına ulaştığında ve hiçbir işleyici tarafından kabul edilmediğinde sessizce kaybolabilir (null döner). Bunu önlemek için zincirin en sonuna bir Default Handler / Fallback Handler eklenmelidir. Bu son halka, işlenemeyen istekler için hata fırlatabilir veya varsayılan bir işlem yürütebilir.
Modern web framework'lerindeki HTTP Middleware yapıları ile bu desenin ilişkisi nedir?
Middleware'ler Sorumluluk Zinciri deseninin en popüler pratik uygulamasıdır. Gelen bir HTTP isteği sırayla AuthMiddleware -> CORSMiddleware -> SanitizeMiddleware zincirinden geçer. Her bir middleware isteği doğrulayabilir, değiştirebilir veya hata durumunda bir sonraki middleware'i çağırmayarak zinciri sonlandırabilir (response döndürür).
Zincir içinde döngüsel (infinite loop) bağımlılıklar nasıl engellenir?
Eğer A işleyicisi B'yi, B işleyicisi de A'yı bir sonraki halka olarak tanımlarsa istekler sonsuz döngüye girer. Bunu engellemek için zincir kurulurken döngüsel bağımlılık kontrolleri yapılmalı veya zincir yapısının doğrusal/tek yönlü (directed acyclic graph) olması kod seviyesinde garanti edilmelidir.
Diğer Tasarım Kalıpları/Design Patterns
Oluşumsal Kalıplar (Creational Patterns)
Factory Method, Abstract Factory, Builder, Prototype, Singleton
Yapısal Kalıplar (Structural Patterns)
Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy
Davranışsal Kalıplar (Behavioral Patterns)
Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor
