Teknik Detaylar

Mediator Tasarım Deseni Nedir?

← Teknik Detaylar
2021-10-03 · 7 dk okuma
Mediator Tasarım Deseni Nedir?
Bu yazıyı yapay zekâ ile tartış
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ç: Birbiriyle etkileşen çok sayıda nesne arasındaki karmaşık ve doğrudan bağımlılıkları (coupling) azaltır. Nesneleri doğrudan haberleştirmek yerine tek bir aracı nesne (Mediator) üzerinden konuşmaya zorlar.
  • Kilit Yapılar: Arabulucu Arayüzü (Mediator), Somut Arabulucu (ConcreteMediator) ve Bileşenler (Components / Colleagues).
  • Motto: Don't talk to each other, talk to the Mediator (Birbirinizle konuşmayın, Arabulucu ile konuşun).

Mediator Tasarım Deseninin Amacı

Mediator (Arabulucu), nesneler arasındaki kaotik bağımlılıkları azaltmayı sağlayan davranışsal (behavioral) bir tasarım desenidir. Bu desen nesneler arasındaki doğrudan iletişimi sınırlar ve sadece aracı bir nesne üzerinden haberleşmelerini sağlar.


Sorun

Uygulamanızda kullanıcı profilleri oluşturmak ve düzenlemek için büyük bir form pencereniz (dialog) olduğunu düşünelim. Form üzerinde metin alanları, onay kutuları (checkbox), butonlar gibi çok sayıda bileşen (UI element) bulunuyor.

Mediator tasarım deseni nedir

Bileşenler geliştikçe aralarındaki etkileşim karmaşık bir hal alır:

  • "Bir evcil hayvanım var" kutusu işaretlendiğinde, evcil hayvan adının girileceği gizli bir metin alanı açılmalıdır.
  • "Gönder" butonuna tıklandığında tüm form elemanlarındaki veriler doğrulanmalıdır (validation).
  • Doğrulama hatası varsa ilgili alanın rengi kırmızı olmalı ve hata mesajı gösterilmelidir.

Mediator tasarım deseni örneği

Eğer bu etkileşim mantığını doğrudan form bileşenlerinin (örneğin onay kutusu veya buton sınıflarının) içine yazarsanız, bu sınıfları başka formlarda tekrar kullanamazsınız. Çünkü onay kutusu sınıfı, formdaki gizli metin alanına sıkı sıkıya bağlı (tightly coupled) hale gelmiştir.


Çözüm

Mediator deseni, bileşenlerin birbiriyle doğrudan konuşmasını yasaklamayı önerir. Bileşenler doğrudan iletişim kurmak yerine, çağrıları ilgili yerlere dağıtan merkezi bir Arabulucu (Mediator) nesnesi ile konuşur.

Bizim örneğimizde diyalog penceresinin (formun) kendisi Mediator görevi görebilir. Formun içindeki tüm bileşenler sadece bu form nesnesine bildirim gönderir.

Mediator tasarım deseni çözüm örneği

Örneğin, gönder butonuna basıldığında buton kendi içine doğrulama kodları yazmaz. Sadece form sınıfına "Bana tıklandı" bildirimi gönderir. Form sınıfı (Mediator) ise tüm elemanları kontrol edip gerekli akışı yönetir. Böylece buton sınıfı formdaki diğer elemanlardan tamamen bağımsızlaşır ve başka projelerde de kolayca kullanılabilir hale gelir.


Gerçek Hayat Senaryosu: Havalimanı Kontrol Kulesi (Air Traffic Control)

Havalimanına iniş ve kalkış yapan uçaklar, pistin uygunluğunu sorgulamak için birbirleriyle doğrudan iletişim kurmazlar. Tüm koordinasyon Kontrol Kulesi üzerinden sağlanır:

// 1. Arabulucu Arayüzü
interface ControlTower {
    public function registerAircraft(Aircraft $aircraft): void;
    public function sendMessage(string $message, Aircraft $sender): void;
}

// 2. Bileşen Arayüzü
abstract class Aircraft {
    protected ControlTower $controlTower;
    public string $flightNumber;

    public function __construct(ControlTower $tower, string $flightNumber) {
        $this->controlTower = $tower;
        $this->flightNumber = $flightNumber;
    }

    abstract public function receive(string $message): void;
}

// 3. Somut Arabulucu
class AirTrafficControlTower implements ControlTower {
    private array $aircrafts = [];

    public function registerAircraft(Aircraft $aircraft): void {
        $this->aircrafts[$aircraft->flightNumber] = $aircraft;
    }

    public function sendMessage(string $message, Aircraft $sender): void {
        foreach ($this->aircrafts as $aircraft) {
            // Mesajı gönderen uçak hariç diğer tüm uçaklara kule üzerinden bilgi verilir
            if ($aircraft !== $sender) {
                $aircraft->receive("Kule'den [{$sender->flightNumber}]: " . $message);
            }
        }
    }
}

// 4. Somut Bileşen
class CommercialAircraft extends Aircraft {
    public function send(string $message): void {
        echo "{$this->flightNumber} mesaj gönderiyor: {$message}\n";
        $this->controlTower->sendMessage($message, $this);
    }

    public function receive(string $message): void {
        echo "{$this->flightNumber} mesajı aldı: {$message}\n";
    }
}

Mediator vs Facade vs Observer

DesenAmaçİletişim Yönü
MediatorSıkı bağlı bileşenlerin iletişimini tek bir merkezde koordine etmek.Çift Yönlü: Bileşenler kuleye haber verir, kule bileşenleri yönetir.
FacadeKarmaşık bir alt sisteme basitleştirilmiş tek bir arayüz sunmak.Tek Yönlü: İstemci Facade'i çağırır, alt sistem Facade'den habersizdir.
ObserverNesneler arasında dinamik, tek taraflı olay aboneliği kurmak.Tek Yönlü: Yayıncı (Publisher) olay fırlatır, aboneler (Subscribers) dinler.

Uygulanabilirlik

  • Sıkı Bağlı Sınıflar: Birbirine çok fazla bağımlı oldukları için değiştirilmesi ve bakımı zor olan bir grup sınıfınız olduğunda kullanın.
  • Yeniden Kullanılamayan Bileşenler: Başka bileşenlere doğrudan bağımlı oldukları için farklı ekranlarda veya uygulamalarda tekrar kullanamadığınız sınıflar olduğunda kullanın.
  • Alt Sınıf Enflasyonu: Sadece bileşenlerin etkileşim mantığını değiştirebilmek için sürekli yeni alt sınıflar (subclasses) oluşturmak zorunda kaldığınızda kullanın.

Diğer Tasarım Desenleri ile İlişkisi

  • Mediator ve Facade benzer görünse de Facade sadece alt sisteme giriş kapısı sunar (yeni işlev eklemez), Mediator ise bileşenlerin birbirleriyle konuşmasını organize eder.
  • Mediator ve Observer sıklıkla birlikte kullanılır. Bileşenlerin arabulucuya olay bildirmesi için Observer tabanlı bir abonelik altyapısı kurulabilir.
  • Chain of Responsibility, gelen isteği sırayla işleyici zincirine aktarırken; Mediator doğrudan alıcı ve gönderici arasındaki bağları koparıp aracı olur.

Mediator Tasarım Deseni Kod Örnekleri

Örnek PHP Kodu

<?php

namespace RefactoringGuru\Mediator\Conceptual;

interface Mediator
{
    public function notify(object $sender, string $event): void;
}

class ConcreteMediator implements Mediator
{
    private Component1 $component1;
    private Component2 $component2;

    public function __construct(Component1 $c1, Component2 $c2)
    {
        $this->component1 = $c1;
        $this->component1->setMediator($this);
        $this->component2 = $c2;
        $this->component2->setMediator($this);
    }

    public function notify(object $sender, string $event): void
    {
        if ($event === "A") {
            echo "Mediator A olayına tepki verdi ve şu işlemleri tetikledi:\n";
            $this->component2->doC();
        }

        if ($event === "D") {
            echo "Mediator D olayına tepki verdi ve şu işlemleri tetikledi:\n";
            $this->component1->doB();
            $this->component2->doC();
        }
    }
}

class BaseComponent
{
    protected ?Mediator $mediator = null;

    public function __construct(?Mediator $mediator = null)
    {
        $this->mediator = $mediator;
    }

    public function setMediator(Mediator $mediator): void
    {
        $this->mediator = $mediator;
    }
}

class Component1 extends BaseComponent
{
    public function doA(): void
    {
        echo "Bileşen 1, A işlemini yapıyor.\n";
        $this->mediator->notify($this, "A");
    }

    public function doB(): void
    {
        echo "Bileşen 1, B işlemini yapıyor.\n";
    }
}

class Component2 extends BaseComponent
{
    public function doC(): void
    {
        echo "Bileşen 2, C işlemini yapıyor.\n";
    }

    public function doD(): void
    {
        echo "Bileşen 2, D işlemini yapıyor.\n";
        $this->mediator->notify($this, "D");
    }
}

$c1 = new Component1();
$c2 = new Component2();
$mediator = new ConcreteMediator($c1, $c2);

echo "İstemci A işlemini tetikliyor:\n";
$c1->doA();

echo "\n";
echo "İstemci D işlemini tetikliyor:\n";
$c2->doD();

Örnek Python Kodu

from __future__ import annotations
from abc import ABC

class Mediator(ABC):
    def notify(self, sender: object, event: str) -> None:
        pass


class ConcreteMediator(Mediator):
    def __init__(self, component1: Component1, component2: Component2) -> None:
        self._component1 = component1
        self._component1.mediator = self
        self._component2 = component2
        self._component2.mediator = self

    def notify(self, sender: object, event: str) -> None:
        if event == "A":
            print("Mediator A olayına tepki verdi ve şu işlemleri tetikledi:")
            self._component2.do_c()
        elif event == "D":
            print("Mediator D olayına tepki verdi ve şu işlemleri tetikledi:")
            self._component1.do_b()
            self._component2.do_c()


class BaseComponent:
    def __init__(self, mediator: Mediator | None = None) -> None:
        self._mediator = mediator

    @property
    def mediator(self) -> Mediator:
        return self._mediator

    @mediator.setter
    def mediator(self, mediator: Mediator) -> None:
        self._mediator = mediator


class Component1(BaseComponent):
    def do_a(self) -> None:
        print("Bileşen 1, A işlemini yapıyor.")
        self.mediator.notify(self, "A")

    def do_b(self) -> None:
        print("Bileşen 1, B işlemini yapıyor.")


class Component2(BaseComponent):
    def do_c(self) -> None:
        print("Bileşen 2, C işlemini yapıyor.")

    def do_d(self) -> None:
        print("Bileşen 2, D işlemini yapıyor.")
        self.mediator.notify(self, "D")


if __name__ == "__main__":
    c1 = Component1()
    c2 = Component2()
    mediator = ConcreteMediator(c1, c2)

    print("İstemci A işlemini tetikliyor:")
    c1.do_a()

    print("\n", end="")

    print("İstemci D işlemini tetikliyor:")
    c2.do_d()

Sıkça Sorulan Sorular (FAQ)

Mediator sınıfının zamanla "God Class" (Aşırı Yüklenmiş Sınıf) haline gelmesi nasıl engellenir?

Eğer sistemdeki tüm iş mantığı ve nesne etkileşimleri tek bir Mediator sınıfının içine yığılır ve kontrol edilmezse, Mediator devasa ve bakımı imkansız bir "God Class" haline gelebilir. Bunu engellemek için:

  1. Sadece koordinasyon kodları Mediator'de kalmalı, asıl iş mantığı bileşenlerin içinde kalmalıdır.
  2. Formlar/diyaloglar büyüdükçe hiyerarşik veya alt Mediator'lar (sub-mediators) tanımlanmalıdır.

Mediator ile Observer desenleri hangi durumlarda birlikte kullanılır?

Bileşenlerin Mediator'e bildirim göndermesi için genellikle Observer deseni kullanılır. Bileşenler birer yayıncı (publisher) gibi davranır ve kendi durum değişikliklerini fırlatır. Mediator ise bu bileşenleri dinleyen tek bir abone (subscriber) rolündedir. Bildirimi alan Mediator gerekli koordinasyon işlemlerini yürütür.

Mediator kullanmanın dezavantajları nelerdir?

  • Merkezi Karmaşıklık: Tüm ilişkiler tek bir sınıfta toplandığı için Mediator sınıfı karmaşıklaşabilir.
  • Hata Ayıklama (Debugging) Zorluğu: Nesneler arasındaki doğrudan bağlar koptuğu için, bir olayın tetiklediği zincirleme reaksiyonları takip etmek ve debug etmek zorlaşabilir.

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