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.
Mediator Tasarım Deseninin Amacı
Mediator (Ara bulucu ) nesneler arasındaki kaotik bağımlılıkları azaltmayı sağlayan davranışsal bir tasarım desenidir. Bu desen nesneler arasındaki doğrudan iletişimi sınırlar ve sadece mediator nesnesi üzerinden haberleşmeye zorlar.
Sorun
Diyelim ki müşteri profilleri oluşturmak ve düzenlemek için bir diyalogunuz var. Bu diyalog metin alanları, işaretleme kutuları (checkbox), düğmeler vb. alanlardan oluşuyor.
Bazı form elemanları diğerleri ile etkileşimde olabilir. Örneğin, “bir köpeğim var” kutusu işaretlenince, köpeğin adının girileceği gizli bir alan görüntülenecek olabilir. Bir diğer örnekte veriyi göndermeden önce kontroller yapılmasını gerektiren bir gönderme butonu olabilir.
Bu mantığı doğrudan form elemanlarının koduna eklerseniz bu elemanların sınıflarını uygulamanızdaki diğer formlarda kullanmanız çok daha zorlaşır. Örneğin işaretleme kutusu elemanını başka bir form içerisinde kullanamazsınız çünkü “köpeğin adı” alanı ile doğrudan bağlantılıdır.
Çözüm
Mediator tasarım deseni birbirinden bağımsız hale getirmek istediğiniz elemanlar arasındaki doğrudan iletişimi kesmeniz gerektiğini önerir. Bu bileşenler doğrudan iletişim kurmak yerine yerine, çağrıları uygun bileşenlere yönlendiren özel bir mediator nesnesi vasıtası ile dolaylı olarak iş birliği yapmalıdır. Böylece bileşenler düzinelerce başka elemana bağlanmak yerine tek bir mediator nesnesine bağımlı olacaklardır.
Profil düzenleme örneğimizde, dialog sınıfı bir mediator olarak görev alabilir. Dialog sınıfı büyük ihtimalle bütün alt elemanlarından haberdardır ve bu sayede yeni bağımlılıklar oluşturmanız gerekmez.
En önemli değişiklik form elemanlarında olur. Gönder butonu özelinde düşünelim. Daha önce kullanıcı gönder butonuna her bastığında tüm form elemanlarının değerlerinin kontrol edilmesi gerekiyordu. Şu an tek yapması gereken diyaloga kendisine tıklandığını haber vermek. Diyalog bu bildirimi aldıktan sonra gereken kontrolleri yapıp işi form elemanlarına aktaracaktır. Böylece gönder butonunun tek bilmesi gereken dialog penceresi oalcaktır.
Biraz daha ileri götürüp tüm diyaloglar için ortak bir arayüz oluşturarak bağımlılığı daha da azaltabilirsiniz. Bu arayüz tüm form elemanlarının diyaloga kendileri ile ilgili olayları haber vermesini sağlayan bir bildirim metodu tanımlamalıdır. Böylece gönder butonumuz artık bu arayüzden türemiş herhangi bir formla entegre çalışabilir.
Uygulanabilirlik
Başka sınıflarla sıkıca bağlı oldukları için değiştirilmesi zor olan sınıflar varsa Mediator desenini kullanabilirsiniz.
Bu desen sınıflar arasındaki ilişkileri ayrı bir sınıfta toplamayı spesifik bir bileşendeki değişikliği diğer bileşenlerden bağımsız kılmayı sağlar.
Başka bileşenlere çok fazla bağımlı olan bir bileşeni başka bir yerde tekrar kullanamıyorsanız bu deseni kullanabilirsiniz.
Mediator desenini uyguladıktan sonra bileşenler diğer bileşenlerden bihaber olacaktır. Bir mediator nesnesi vasıtası ile birbirleriyle dolaylı olarak iletişim kurabilirler. Bir bileşeni başka bir uygulamada kullanmak istediğinizde başka bir mediator nesnesi kullanmanız gerekir.
Sadece bazı temel davranışları yeniden kullaabilmek için bir sürü bileşen alt sınıfı oluşturmanız gerekiyorsa Mediator desenini kullanabilirsiniz.
Bileşenler arasındaki tüm ilişkiler mediator içinde tutulduğu için, yeni mediator sınıfları oluşturup, bileşenleri değiştirmeden farklı iş birliği yöntemleri tanımlamak çok daha kolay olacaktır.
Diğer tasarım desenleri/kalıpları ile ilişkisi
- Chain of Responsibility bir isteği potansiyel alıcılardan en az biri işleyene kadar dinamik bir potansiyel alıcı zinciri boyunca sırayla iletir.
- Command göndericiler ve alıcılar arasında tek yönlü bağlantılar kurar.
- Mediator göndericiler ve alıcılar arasındaki doğrudan bağlantıları ortadan kaldırarak onları bir aracı nesne aracılığıyla dolaylı olarak iletişim kurmaya zorlar.
- Observer alıcıların isteklere dinamik olarak abone olmalarını ve abonelikten çıkmalarını sağlar.
- Facade ve Mediator‘ün benzer görevleri vardır; her ikiside birbiriyle sıkı sıkıya bağlı bir çok nesnenin bir arada çalışmasını organize eder.
- Facade bir nesne sistemi için basitleştirilmiş bir arayüz sunar, fakat yeni bir işlevsellik kazandırmaz. Alt sistem Facade’den haberdar değildir. Alt sistem içerisindeki nesneler birbirleriyle direkt iletişimde olurlar.
- Mediator bir sistemde bileşenler arası iletişimi merkezileştirir. Bileşenler sadece Mediator nesnesinden haberdardır ve birbirleri arasında iletişim yoktur.
- Mediator ve Observer arasındaki fark genellikle belirgin değildir. Çoğu durumda bu kalıplardan herhangi birini uygulayabilirsiniz fakat bazen ikisini aynı anda uygulamanız gerekebilir. Bunu nasıl yapabileceğimize bakalım.
Mediator’ün birincil amacı, bir dizi sistem bileşeni arasındaki karşılıklı bağımlılıkları ortadan kaldırmaktır. Bileşenler bunun yerine tek bir aracı nesneye bağımlı hale gelir. Observer’ın amacı ise nesneler arasında bazılarının diğerlerinin alt nesnesi olarak hareket ettiği, dinamik tek taraflı bir bağlantı oluşturmaktır.
Mediator deseninin Observer tabanlı popüler bir uygulama yöntemi var. Bu yöntemde mediator yayıncı, bileşenler ise mediator’ün olaylarına (event) abone olan (subscribe) ya da abonelikten çıkan (unsubscribe) aboneler olarak hareket ederler. Mediator bu şekilde inşa edildiğinde Observer’a çok benzer.
Mediator ve Observer’ın ortak noktaları vardır ve bazen birbirleriyle iç içe girebilirler, ama birbirlerinden tamamen farklı kullanımları da vardır.
Mediator desenini farklı şekillerde uygulayabileceğinize de dikkat edin. Örneğin tüm bileşenleri kalıcı olarak bir mediator nesnesine bağlayabilirsiniz. Bu uygulama Observer’a benzemez ve hala bir Mediator desenidir.
Şimdi tüm bileşenlerin yayıncı haline geldiği ve birbirileri arasında dinamik bağlantılara izin verilen bir program hayal edin. Burada merkezi bir mediator nesnesi yokken, dağıtık bir observer yapısı olabilir.
Mediator Deseni Kod Örnekleri
Örnek PHP Kodu
<?php
namespace RefactoringGuru\Mediator\Conceptual;
/**
* The Mediator interface declares a method used by components to notify the
* mediator about various events. The Mediator may react to these events and
* pass the execution to other components.
*/
interface Mediator
{
public function notify(object $sender, string $event): void;
}
/**
* Concrete Mediators implement cooperative behavior by coordinating several
* components.
*/
class ConcreteMediator implements Mediator
{
private $component1;
private $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 reacts on A and triggers following operations:\n";
$this->component2->doC();
}
if ($event == "D") {
echo "Mediator reacts on D and triggers following operations:\n";
$this->component1->doB();
$this->component2->doC();
}
}
}
/**
* The Base Component provides the basic functionality of storing a mediator's
* instance inside component objects.
*/
class BaseComponent
{
protected $mediator;
public function __construct(Mediator $mediator = null)
{
$this->mediator = $mediator;
}
public function setMediator(Mediator $mediator): void
{
$this->mediator = $mediator;
}
}
/**
* Concrete Components implement various functionality. They don't depend on
* other components. They also don't depend on any concrete mediator classes.
*/
class Component1 extends BaseComponent
{
public function doA(): void
{
echo "Component 1 does A.\n";
$this->mediator->notify($this, "A");
}
public function doB(): void
{
echo "Component 1 does B.\n";
$this->mediator->notify($this, "B");
}
}
class Component2 extends BaseComponent
{
public function doC(): void
{
echo "Component 2 does C.\n";
$this->mediator->notify($this, "C");
}
public function doD(): void
{
echo "Component 2 does D.\n";
$this->mediator->notify($this, "D");
}
}
/**
* The client code.
*/
$c1 = new Component1();
$c2 = new Component2();
$mediator = new ConcreteMediator($c1, $c2);
echo "Client triggers operation A.\n";
$c1->doA();
echo "\n";
echo "Client triggers operation D.\n";
$c2->doD();
Örnek Python Kodu
from __future__ import annotations
from abc import ABC
class Mediator(ABC):
"""
The Mediator interface declares a method used by components to notify the
mediator about various events. The Mediator may react to these events and
pass the execution to other components.
"""
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 reacts on A and triggers following operations:")
self._component2.do_c()
elif event == "D":
print("Mediator reacts on D and triggers following operations:")
self._component1.do_b()
self._component2.do_c()
class BaseComponent:
"""
The Base Component provides the basic functionality of storing a mediator's
instance inside component objects.
"""
def __init__(self, mediator: Mediator = None) -> None:
self._mediator = mediator
@property
def mediator(self) -> Mediator:
return self._mediator
@mediator.setter
def mediator(self, mediator: Mediator) -> None:
self._mediator = mediator
"""
Concrete Components implement various functionality. They don't depend on other
components. They also don't depend on any concrete mediator classes.
"""
class Component1(BaseComponent):
def do_a(self) -> None:
print("Component 1 does A.")
self.mediator.notify(self, "A")
def do_b(self) -> None:
print("Component 1 does B.")
self.mediator.notify(self, "B")
class Component2(BaseComponent):
def do_c(self) -> None:
print("Component 2 does C.")
self.mediator.notify(self, "C")
def do_d(self) -> None:
print("Component 2 does D.")
self.mediator.notify(self, "D")
if __name__ == "__main__":
# The client code.
c1 = Component1()
c2 = Component2()
mediator = ConcreteMediator(c1, c2)
print("Client triggers operation A.")
c1.do_a()
print("\n", end="")
print("Client triggers operation D.")
c2.do_d()