Bridge Pattern

Bridge pattern yapısal kompozisyonlara yönelik üretilen çözümlerden bir tanesidir. Bir sınıfın içerisinde bulunan elemanların çeşitlilik yarattığı durumlarda oluşan karmaşayı çözmektedir. Bu çeşitliliği yaratan yapıların ana sınıftan soyutlanması ile ana sınıfın implementasyon detaylarını bilmesinden kurtulmuş oluruz.

Bu tasarım kalıbını daha net bir örnekle açıklamak için bir kurguya gidelim. Günümüzde bazı şirketler müşterilerine reklam veya bilgilendirme amaçlı bildirimler gönderebilmektedir. Bu bildirimler ise e-posta, SMS veya anlık mesajlaşma bildirimleri gibi yöntemlerle gerçekleşebilir. Biz de bu konuyu düşünerek sorunlu bir durum oluşturalım. Varsayalım ki şirketimiz göndereceği bildirimlerin içeriğini, bildirimin türüne göre değiştirmek istiyor. Örneğin, şirketimiz, anlık mesajlaşma ve SMS bildirimlerini düz yazı ile iletirken, e-posta bildirimlerini ise pdf olarak iletiyor olsun. Bu durumda toplamda 3 farklı bildirim yöntemi ve 2 farklı içerik tipi doğmakta. Bunları kartezyen bir liste haline getirelim.

  • Anlık mesajlaşma ve düz yazı -> Mümkün
  • Anlık mesajlaşma ve pdf -> Mümkün değil
  • SMS ve düz yazı -> Mümkün
  • SMS ve pdf -> Mümkün değil
  • E-posta ve düz yazı -> Mümkün değil
  • E-posta ve pdf -> Mümkün

Toplamda 6 farklı durum, 6 farklı if koşulu ile kontrol edilebilir. Lakin bu programa 1 yeni bildirim tipi veya 1 yeni gönderim şekli eklendiğinde bu if bloğunu elden geçirmek durumunda kalırsınız. Dahası bu karar parametrelerine yenileri eklenirse sınıfınız yönetilemez hale gelebilir. Bu durumu kod üzerinde görelim.

Öncelikle bildirim tiplerimizi bir enum olarak sakladığımızı hayal edelim. Ardından da ana bildirim gönderimi akışımızı kodlayalım. Bu kodu da bir main methodu içerisinden çağıralım.

package com.ucemucar.notification;

public enum NotificationType {
	
	SMS,IM,EMAIL

}
package com.ucemucar.notification;

public class NotificationManager {

	public void sendNotification(NotificationType notificationType) {
		
		if (notificationType.equals(NotificationType.SMS)) {
			System.out.println("Notification sent! (SMS + Text)");
		} else if (notificationType.equals(NotificationType.IM)) {
			System.out.println("Notification sent! (IM + Text)");
		} else if (notificationType.equals(NotificationType.EMAIL)) {
			System.out.println("Notification sent! (EMAIL + pdf)");
		} else {
			System.out.println("Please choose a valid type");
		}
		 
	}
	
}
public class NotificationService {

	public static void main(String[] args) {
		sendNotification();
	}
	
	
	public static void sendNotification() {
		
		NotificationManager notificationManager = new NotificationManager();
		
		notificationManager.sendNotification(NotificationType.SMS);
		notificationManager.sendNotification(NotificationType.IM);
		notificationManager.sendNotification(NotificationType.EMAIL);
		
	}
}
Çıktı:
Notification sent! (SMS + Text)
Notification sent! (IM + Text)
Notification sent! (EMAIL + pdf)

Buradaki karmaşa NotificationManager sınıfında yaşanmaktadır. Dikkat ederseniz sınıfı yeni bir bildirim türü ile genişletmek istediğimizde ana akışa dokunmak zorunda kalmaktayız. Üstelik burada birbirine uyumlu içerik ve yöntem kontrolü bulunmuyor. Bunun da akışa eklenmesi ile son derece kompleks bir kod elde ederiz. Bu durumda akışın buradaki içerik bilgilerinden soyutlanması mantıklıdır. Zira bildirim gönderimi temelde yöntem-içerik ikilisini yönettiğimiz basit bir konudur. Konuyu karmaşıklaştıran şey buradaki farklılıklar olmuştur. O halde yöntemleri ve içerikleri sınıflara bölmek makuldur. Böylece akış içerisindeki rolleri sınıfların sorumluluğu haline getirebiliriz. Bu durumu da notification2 paketi altında görelim.

public class SMS {
	
	public void sendSMSNotification() {
		System.out.println("sendSMSNotification");
	}

}

public class IM {
	
	public void sendIMNotification() {
		System.out.println("sendIMNotification");
	}
	
}

public class Email {
	
	public void sendEmailNotification() {
		System.out.println("sendEmailNotification");
	}
}

public class NotificationManagerModified {

	private SMS sms;

	private IM im;

	private Email email;

	public NotificationManagerModified() {

		this.sms = new SMS();
		this.im = new IM();
		this.email = new Email();
	}

	public void sendSMSNotification() {
		sms.sendSMSNotification();
	}

	public void sendIMNotification() {
		im.sendIMNotification();
	}

	public void sendEmailNotification() {
		email.sendEmailNotification();
	}

}

Görüldüğü üzere bu sınıfı fazla sayıda karar noktasından kurtarmış bulunuyoruz. Artık karar noktalarını tüketici sınıfa bırakmış olacağız. İşleyişe kısaca bir göz atalım.

	public static void sendNotificationModified() {
		
		NotificationManagerModified notificationManager = new NotificationManagerModified();
		
		notificationManager.sendSMSNotification();
		notificationManager.sendIMNotification();
		notificationManager.sendEmailNotification();
		
	}
Çıktı:
sendSMSNotification
sendIMNotification
sendEmailNotification

Bu akış bizi her ne kadar ana sınıfımızdaki karmaşadan kurtarsa da karmaşayı başka bir yere devretmiş olduk. Bu durum sorunu çözmemiş olur. Yani bizim farklı bir tasarıma ihtiyacımız vardır. Kaldı ki yeni bir yöntem veya içerik türü geldiğinde yeni methodlar eklememiz ve bu methodları yürütecek sınıfları manager sınıfına bağlı kılmamız gerekecekti. Çözüm için uygulamamızı yeniden tasarlayalım. Öncelikle içerik türlerini bir arayüz arkasına alalım. Interface gövdesine içerik tiplerinin kendi işlerinin kodlanabildiği methodları ekleyeceğiz.

public interface NotificationContent {
	
	public String getContent();

}

public class Text implements NotificationContent {

	@Override
	public String getContent() {
		
		return "Text notification"; 
	}

}

public class Pdf implements NotificationContent {

	@Override
	public String getContent() {
		
		return "Pdf notification";
	}

}

Bildirim gönderimini içerik ve yöntem ikilisi olarak tariflemiştik. Şimdi de yöntemleri bir interface arkasına alalım.

public abstract class NotifictionMethod {
	
	NotificationContent content;

	public NotifictionMethod(NotificationContent content) {
		this.content = content;
	}

	abstract void sendNotification();
}

public class SMS extends NotifictionMethod {

	public SMS(NotificationContent content) {
		super(content);
	}

	@Override
	public void sendNotification() {
		
		System.out.println("SMS Notification " + content.getContent());
		
	}

}

public class IM extends NotifictionMethod {

	public IM(NotificationContent content) {
		super(content);
	}

	@Override
	public void sendNotification() {
		
		System.out.println("IM Notification " + content.getContent());
		
	}

}

public class Email extends NotifictionMethod {

	public Email(NotificationContent content) {
		super(content);
	}

	@Override
	public void sendNotification() {
		
		System.out.println("Email Notification " + content.getContent());
		
	}

}

Daha evvel manager ismi taşıyan sınıfımızın tasarımını da bu yaklaşımla tamamen operasyonel bir hale getirebiliriz. Bu cümledeki kasıt, sınıfın içerik türleri ve gönderim yöntemlerinden tamamen soyutlanıp, sadece gönderim işinin doğasıyla ilgilenmesidir. Böylece sınıfımız kompozisyonu dışarıdaki sınıfa bırakır, sadece ve sadece gönderim yapar ve ayrıca -en önemlisi- yeni gelen gönderim şekilleri ve içeriklerden etki almadan yoluna devam eder.

public class NotificationBridge {

	public void sendNotification(NotifictionMethod method) {
		
		method.sendNotification();
	}
	
}

Son olarak bridge pattern uygulanmış güncel durumun koduna göz atalım.

	public static void sendNotificationViaBridge() {
		
		NotificationBridge bridge = new NotificationBridge();
		
		Pdf pdf = new Pdf();
		Text text = new Text();
		
		IM instantMessage = new IM(text);
		SMS sms = new SMS(text);
		Email email = new Email(pdf);
		
		bridge.sendNotification(instantMessage);
		bridge.sendNotification(sms);
		bridge.sendNotification(email);
		
		
	}
Çıktı:
IM Notification Text notification
SMS Notification Text notification
Email Notification Pdf notification

Bridge pattern open/closed prensibine doğrudan hizmet eden tasarım kalıplarımızdandır. Bu tasarım kalıbı nesnelerin yaratılma şeklinden ziyade sınıf akışlarının sabitlenmesi motivasyonuna sahiptir. Programlarda bulunan ana akışın bağımsızlığını güçlendirir. Entegratör daha yüksek seviyeli arayüzlerle çalışır ve implementasyon detaylarıyla ilgilenmek durumunda kalmaz. Böylece ana akışın tek görevi implemetasyondan bağımsız kalarak doğru gidişatı çalıştırmaktır. Bu da sınıfımızın single responsibility prensibinde daha güçlü konuma gelmesini sağlar. Öte yandan interface ve sınıflar noktasında sayı artışı yaşatır.

Leave a Reply

Your email address will not be published. Required fields are marked *