KÜRE LogoKÜRE Logo
Ai badge logo

Bu madde yapay zeka desteği ile üretilmiştir.

BlogGeçmiş
Blog
Avatar
Ana YazarSinan Turan22 Nisan 2025 13:06

Davranışsal(Behavioral) Tasarım Kalıpları

fav gif
Kaydet
kure star outline

1. Chain of Responsibility (Sorumluluk Zinciri)

Chain of Responsibility tasarım kalıbı, bir isteği işleyecek nesneyi dinamik olarak belirlemek için nesneleri zincir halinde birbirine bağlar. Bu zincirde her bir nesne isteği işler veya zincirdeki bir sonraki nesneye iletir.

->Ne Zaman Kullanılır?

  • İsteklerin göndericiyle işlemcinin ayrılması isteniyorsa,
  • Birden fazla nesne isteği işleyebilecekse ama hangisinin işleyeceği çalışma zamanında belli olacaksa,

Zincirdeki nesnelerin sıralaması veya sayısı değiştirilebilirse.

->Gerçek Hayat Analojisi

Bir müşteri destek sistemi düşünelim: kullanıcı önce chatbot ile görüşür, sorunu çözülmezse insan destek personeline aktarılır, yine çözülmezse süpervizöre iletilir. Bu sistem tam anlamıyla bir sorumluluk zinciridir.

->Kod Örneği

// Handler (Soyut işleyici)
abstract class Handler {
    private Handler next;
    public Handler setNext(Handler next) {
        this.next = next;
        return next;
    }
    public void handle(String request) {
        if (next != null) {
            next.handle(request);
        }
    }
}
// ConcreteHandler1
class AuthHandler extends Handler {
    public void handle(String request) {
        if (request.equals("auth")) {
            System.out.println("Kullanıcı doğrulandı.");
        } else {
            super.handle(request);
        }
    }
}
// ConcreteHandler2
class LoggingHandler extends Handler {
    public void handle(String request) {
        if (request.equals("log")) {
            System.out.println("İstek loglandı.");
        } else {
            super.handle(request);
        }
    }
}
// ConcreteHandler3
class CompressionHandler extends Handler {
    public void handle(String request) {
        if (request.equals("compress")) {
            System.out.println("İstek sıkıştırıldı.");
        } else {
            super.handle(request);
        }
    }
}
public class Main {
    public static void main(String[] args) {
        Handler auth = new AuthHandler();
        Handler log = new LoggingHandler();
        Handler compress = new CompressionHandler();
        auth.setNext(log).setNext(compress);
        auth.handle("log");
        auth.handle("compress");
        auth.handle("auth");
        auth.handle("invalid");  // zincirde karşılığı yok
    }
}

->Avantajları

Chain of Responsibility tasarım kalıbı, gönderici ile isteği işleyen nesne arasındaki bağımlılığı ortadan kaldırarak dikkat çekmektedir. Bu yapı sayesinde, istekleri işleyecek olan nesneler bir zincir oluşturur ve her bir nesne, zincirdeki bir sonraki nesneye isteği iletme veya isteği kendi bünyesinde işleme yeteneğine sahiptir. Bu durum, zincirin kolaylıkla genişletilmesine ve yeni işleme adımlarının mevcut yapıya entegre edilmesine olanak tanır. Ayrıca, istemci açısından hangi nesnenin söz konusu isteği nihai olarak işlediğinin bir önemi bulunmamaktadır; önemli olan isteğin zincirdeki uygun bir nesne tarafından ele alınmasıdır.

->Dezavantajları

Chain of Responsibility tasarım kalıbının potansiyel dezavantajları da bulunmaktadır. Her bir isteğin zincirdeki tüm nesneleri potansiyel olarak dolaşması, özellikle uzun zincirlerde performans maliyetine yol açabilir. Ayrıca, belirli bir isteği işleyecek uygun nesnenin zincirde bulunup bulunmadığı ancak çalışma zamanında kesinleşir. Bu durum, bazı isteklerin hiçbir zaman işlenmemesi riskini beraberinde getirebilir.

2.Command (Komut)

Command tasarım kalıbı, bir isteği nesne olarak kapsüller. Bu sayede isteği kuyruklayabilir, loglayabilir veya geri alabilir (undo) hale getirebiliriz. Ayrıca, istemci nesne ile işlem yapılacak nesne arasında gevşek bir bağlılık kurar.

->Ne Zaman Kullanılır

  • İsteklerin bir kuyrukta saklanması, sıralanması ya da günlüğe kaydedilmesi gerekiyorsa,
  • Geri alma (undo) işlemleri yapılacaksa,
  • İşlemleri zamanlayarak daha sonra gerçekleştirmek isteniyorsa,
  • Gönderici ile alıcı arasında sıkı bağımlılık istenmiyorsa.

->Gerçek Hayat Analojisi

Bir garsonun restorandaki siparişleri alıp mutfağa iletmesi. Garson sadece siparişi alır (komut), ama ne pişirileceğini şef bilir (alıcı). Garson ve şef birbirlerinin detaylarını bilmek zorunda değildir.

->Kod Örneği

// Receiver
class Editor {
    public void save() {
        System.out.println("Dosya kaydedildi.");
    }
    public void open() {
        System.out.println("Dosya açıldı.");
    }
}
// Command Arayüzü
interface Command {
    void execute();
}
// Concrete Command'lar
class SaveCommand implements Command {
    private Editor editor;
    public SaveCommand(Editor editor) {
        this.editor = editor;
    }
    public void execute() {
        editor.save();
    }
}
class OpenCommand implements Command {
    private Editor editor;
    public OpenCommand(Editor editor) {
        this.editor = editor;
    }
    public void execute() {
        editor.open();
    }
}
// Invoker
class CommandManager {
    private Command command;
    public void setCommand(Command command) {
        this.command = command;
    }
    public void executeCommand() {
        if (command != null) {
            command.execute();
        }
    }
}
//Kullanım
public class Main {
    public static void main(String[] args) {
        Editor editor = new Editor();
        Command open = new OpenCommand(editor);
        Command save = new SaveCommand(editor);
        CommandManager manager = new CommandManager();
        manager.setCommand(open);
        manager.executeCommand();
        manager.setCommand(save);
        manager.executeCommand();
    }
}

->Avantajları

Command tasarım kalıbı, operasyonları nesneler halinde kapsülleyerek, komutların loglanabilmesine ve geçmişe dönük olarak geri alınabilmesine olanak tanır. Bu sayede, gerçekleştirilen işlemlerin kaydı tutulabilir ve istenildiğinde önceki durumlara dönülebilir. Ek olarak, komutlar belirli bir zamanlamaya göre veya belirli bir sırada yürütülmek üzere sıraya alınabilir, bu da karmaşık işlem akışlarının yönetilmesini kolaylaştırır. Önemli bir avantajı ise, komutları tetikleyen gönderici ile bu komutları fiilen uygulayan alıcı arasındaki ayrımı netleştirerek, bu iki bileşen arasındaki bağımlılığı önemli ölçüde azaltmasıdır. Bu sayede, sistemin farklı bölümleri arasındaki耦合 (coupling) düşürülerek daha esnek ve sürdürülebilir bir yapı elde edilir.

->Dezavantajları

Command tasarım kalıbının bazı dezavantajları da bulunmaktadır. Sisteme eklenen her bir farklı işlem için ayrı bir komut sınıfı oluşturulması gerekebilir, bu da zamanla komut sınıfı sayısının önemli ölçüde artmasına yol açabilir. Dolayısıyla, özellikle çok sayıda farklı işlem türünün olduğu sistemlerde, bu durum genel yapısal karmaşıklığı artırabilir ve yönetimi zorlaştırabilir.

3.Iterator (Yineleyici)

Iterator tasarım kalıbı, bir koleksiyonun elemanları üzerinde koleksiyonun iç yapısını bilmeden dolaşmayı sağlar. Böylece liste, dizi, ağaç gibi yapılara dışarıdan erişim kolaylaşır.

 ->Ne Zaman Kullanılır?

  • Karmaşık veri yapıları üzerinde, yapının iç detaylarını bilmeden gezinmek istendiğinde.
  • Farklı türde koleksiyonlar üzerinde benzer biçimde gezinmek istendiğinde.
  • Koleksiyonun gezinme algoritmasını nesne haline getirmek istendiğinde.

->Gerçek Hayat Analojisi

Bir müzik çalar düşünün. Bir çalma listesinde önceki, sonraki şarkıya geçebilirsiniz ama müzik çaların çalma listesinin nasıl tutulduğunu bilmek zorunda değilsiniz. Sadece iterator size şarkı şarkı dolaşma imkânı verir.

->Kod Örneği

// Koleksiyon Arayüzü
interface SocialNetwork {
    ProfileIterator createFriendsIterator(String profileEmail);
    ProfileIterator createCoworkersIterator(String profileEmail);
}
// Iterator Arayüzü
interface ProfileIterator {
    boolean hasNext();
    Profile getNext();
}
// Concrete Iterator
class FacebookIterator implements ProfileIterator {
    private Facebook facebook;
    private String profileEmail;
    private String type;
    private int currentPosition = 0;
    private List<Profile> cache;
    public FacebookIterator(Facebook facebook, String profileEmail, String type) {
        this.facebook = facebook;
        this.profileEmail = profileEmail;
        this.type = type;
        this.cache = facebook.requestProfileFriendsFromFacebook(profileEmail, type);
    }
    public boolean hasNext() {
        return currentPosition < cache.size();
    }
    public Profile getNext() {
        if (!hasNext()) return null;
        return cache.get(currentPosition++);
    }
}
// Concrete Collection (Koleksiyonun gerçek hali)
class Facebook implements SocialNetwork {
    public ProfileIterator createFriendsIterator(String profileEmail) {
        return new FacebookIterator(this, profileEmail, "friends");
    }
    public ProfileIterator createCoworkersIterator(String profileEmail) {
        return new FacebookIterator(this, profileEmail, "coworkers");
    }
    // Simülasyon amaçlı veri çeken metot
    public List<Profile> requestProfileFriendsFromFacebook(String profileEmail, String type) {
        System.out.println(profileEmail + " için " + type + " listesi alındı.");
        return List.of(new Profile("Ahmet"), new Profile("Ayşe"));
    }
}
// Profil sınıfı (veri)
class Profile {
    private String name;
    public Profile(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}

->Avantajları

Iterator tasarım kalıbı, temel olarak farklı koleksiyon yapılarının içeriğine, bu yapıların iç işleyişinden bağımsız bir şekilde erişim ve gezinme imkanı sunar. Bu örüntü sayesinde, farklı veri yapıları (listeler, kümeler, ağaçlar vb.) üzerinde yineleme işlemleri gerçekleştirmek için standart bir arayüz tanımlanabilir. Dahası, yineleyici örüntüsü, farklı gezinme algoritmalarının (örneğin, derinlemesine, genişlemesine) somut koleksiyon yapısından ayrıştırılmasına olanak tanır, böylece farklı ihtiyaçlara uygun özelleştirilmiş gezinme stratejileri uygulanabilir. Son olarak, bu örüntü, farklı koleksiyonlar üzerinde tekrar eden gezinme işlemlerini gerçekleştirmek için yazılması gereken kod miktarını önemli ölçüde azaltarak, kodun daha soyut, okunabilir ve yeniden kullanılabilir olmasını sağlar.

->Dezavantajları

Iterator tasarım kalıbının bazı durumlarda dikkate alınması gereken potansiyel dezavantajları da bulunmaktadır. Özellikle yineleme sırasında koleksiyonun bir kopyasının oluşturulması gerektiği senaryolarda, bu durum performans üzerinde ek bir maliyet yaratabilir. Ayrıca, farklı yineleme davranışlarını desteklemek için birden fazla somut yineleyici sınıfının oluşturulması gerekebilir, bu da sistemdeki toplam sınıf sayısını artırabilir ve dolayısıyla genel karmaşıklığı etkileyebilir.

4.Interpreter (Yorumlayıcı)

Interpreter tasarım kalıbı, bir dili temsil eden grameri (kuralları) nesneleştirerek, cümleleri (ifadeleri) yorumlamak için kullanılır. Yani bu desen sayesinde kendi mini programlama dilinizi yazabilir ve bu dili yorumlayarak çalıştırabilirsiniz.

->Ne Zaman Kullanılır?

  • Belirli bir grameri olan bir dilin ifadelerini yorumlamak istendiğinde.
  • Küçük, basit ve tekrar eden yorumlama kuralları olan durumlarda.
  • Matematiksel ifadelerin, Boolean ifadelerinin ya da özel komut dillerinin yorumlanmasında.

->Gerçek Hayat Analojisi

Basit bir hesap makinesi düşünün. "5 + 3 - 2" gibi bir ifadeyi yorumlayıp sonuç üretmesi gerekir. Her sayı ve işlem birer semboldür. Interpreter deseni bu sembolleri çözümleyerek işlemin sonucunu verir.

->Kod Örneği

Bu örnek, matematiksel ifadeleri (örneğin: "1 + 2") yorumlayan basit bir dil üzerine kuruludur.

interface Expression {
    boolean interpret(String context);
}
class TerminalExpression implements Expression {
    private String data;
    public TerminalExpression(String data) {
        this.data = data;
    }
    public boolean interpret(String context) {
        return context.contains(data);
    }
}
class OrExpression implements Expression {
    private Expression expr1;
    private Expression expr2;
    public OrExpression(Expression expr1, Expression expr2) {
        this.expr1 = expr1;
        this.expr2 = expr2;
    }
    public boolean interpret(String context) {
        return expr1.interpret(context) || expr2.interpret(context);
    }
}
class AndExpression implements Expression {
    private Expression expr1;
    private Expression expr2;
    public AndExpression(Expression expr1, Expression expr2) {
        this.expr1 = expr1;
        this.expr2 = expr2;
    }
    public boolean interpret(String context) {
        return expr1.interpret(context) && expr2.interpret(context);
    }
}
public class InterpreterPatternDemo {
    // “Robert” veya “John” olan bir ifade
    public static Expression getMaleExpression() {
        Expression robert = new TerminalExpression("Robert");
        Expression john = new TerminalExpression("John");
        return new OrExpression(robert, john);
    }
    // “Julie” ve “Married” olan bir ifade
    public static Expression getMarriedWomanExpression() {
        Expression julie = new TerminalExpression("Julie");
        Expression married = new TerminalExpression("Married");
        return new AndExpression(julie, married);
    }
public static void main(String[] args) {
        Expression isMale = getMaleExpression();
        Expression isMarriedWoman = getMarriedWomanExpression();
        System.out.println("John erkek mi? " + isMale.interpret("John")); // true
        System.out.println("Julie evli kadın mı? " + isMarriedWoman.interpret("Married Julie")); // true
    }
}

->Avantajları

Interpreter tasarım kalıbı, dilbilgisi kurallarını nesneler aracılığıyla temsil etme prensibine dayanır. Bu yaklaşım, dilbilgisinin yapısını doğrudan kod içinde yansıtarak, dilin kolayca genişletilmesine ve yeni dilbilgisi ifadelerinin sisteme rahatlıkla entegre edilmesine olanak tanır. Bu özelliği sayesinde, yorumlayıcı örüntüsü, belirli bir alana özgü dillerin (Domain-Specific Language - DSL) geliştirilmesi için oldukça uygun bir temel teşkil eder. DSL'ler, belirli bir problem alanını ifade etmek için optimize edilmiş, sınırlı kapsamlı dillerdir ve yorumlayıcı örüntüsü, bu tür dillerin sözdizimsel ve anlamsal yapısının modellenmesinde etkili bir araç sunar.

->Dezavantajları

Interpreter tasarım kalıbının potansiyel dezavantajları da bulunmaktadır. Özellikle karmaşık dilbilgisi kurallarının söz konusu olduğu durumlarda, her bir kural veya kural kombinasyonu için ayrı ayrı sınıfların oluşturulması gerekebilir. Bu durum, sistemdeki sınıf sayısının önemli ölçüde artmasına ve dolayısıyla yönetim zorluklarına yol açabilir. Ek olarak, yorumlama sürecinde çok sayıda küçük nesnenin oluşturulması ve yönetilmesi gerekebileceğinden, bu durum bazı senaryolarda performans sorunlarına neden olabilir.

5.Mediator (Arabulucu)

Mediator (Arabulucu) tasarım deseni, nesneler arasında doğrudan iletişimi engelleyerek, bu iletişimi merkezi bir nesne üzerinden gerçekleştirmeyi sağlar. Böylece bileşenler arasındaki bağımlılığı azaltır ve sistemin bakımını kolaylaştırır.

->Ne Zaman Kullanılır

  • Nesneler arası çok sayıda karmaşık iletişim varsa.
  • Bir bileşenin diğer bileşenleri bilmeden onlarla haberleşmesi gerekiyorsa.
  • Bağımlılıkları azaltmak ve daha okunabilir bir yapı kurmak isteniyorsa.

->Gerçek Hayat Analojisi

Bir kuledeki kontrol odası (mediator), tüm pilotların birbirine karışmadan iniş-kalkış yapmasını sağlar. Pilotlar birbirleriyle doğrudan değil, kontrol odası aracılığıyla haberleşirler.

->Kod Örneği

Bu örnekte, farklı kullanıcılar bir sohbet odasında (ChatRoom) arabulucu vasıtasıyla iletişim kurar.

class ChatRoom {
    public static void showMessage(User user, String message){
        System.out.println("[" + user.getName() + "] : " + message);
    }
}
class User {
    private String name;
    public User(String name){
        this.name  = name;
    }
    public String getName(){
        return name;
    }
    public void sendMessage(String message){
        ChatRoom.showMessage(this, message);
    }
}
public class MediatorPatternDemo {
    public static void main(String[] args) {
        User robert = new User("Robert");
        User john = new User("John");
        robert.sendMessage("Selam, John!");
        john.sendMessage("Merhaba, Robert!");
    }
}
//Çıktı
[Robert] : Selam, John!
[John] : Merhaba, Robert!

->Avantajları

Mediator (Arabulucu) tasarım deseni, sistemdeki farklı nesneler arasındaki doğrudan iletişimi engelleyerek, bu nesneler arasındaki sıkı bağımlılığı ortadan kaldırmayı hedefler. Bunun yerine, tüm karmaşık iletişim akışını merkezi bir arabulucu nesnesi üzerinden yönetir. Bu sayede, sistemdeki çoklu ve karmaşık iletişim yolları tek bir noktada toplanarak sadeleştirilir. Sonuç olarak, sistemdeki herhangi bir nesnenin davranışında veya iletişiminde değişiklik yapmak gerektiğinde, bu değişiklikler yalnızca arabulucu üzerinde yapılır, diğer nesnelerin etkilenme olasılığı azalır ve bakım süreci kolaylaşır.

->Dezavantajları

Mediator (Arabulucu) tasarım deseninin sunduğu avantajların yanı sıra, potansiyel riskleri de bulunmaktadır. En belirgin risklerden biri, arabulucu sınıfının sistemdeki tüm iletişimi yönetmesi nedeniyle zamanla çok fazla sorumluluk yüklenmesi ve "Tanrı Nesnesi" (God Object) olarak adlandırılan anti-pattern'e dönüşme olasılığıdır. Bu durumda, arabulucu sınıfı aşırı derecede büyüyebilir ve bakımı zorlaşabilir. Ayrıca, nesneler arasındaki karmaşık iletişim mantığı arabulucu içinde yoğunlaşabileceği için, bu durum arabulucunun kendisinin de karmaşıklaşmasına ve anlaşılması güç bir yapıya bürünmesine neden olabilir.

6.Memento (Hatıra/Anı)

Memento deseni, bir nesnenin önceki durumunu kaydedip daha sonra bu duruma geri dönebilmek için kullanılır. Bu desen, nesnenin iç yapısını dışa açmadan geri alma (undo) işlevselliği sunar.

->Ne Zaman Kullanılır?

  • Kullanıcının yaptığı işlemleri geri almasını (undo) sağlamak isteniyorsa.
  • Nesnenin geçmiş durumlarını saklamak gerekiyorsa.
  • Nesnenin iç durumu dışarıdan doğrudan erişilmeden korunmak isteniyorsa.

->Gerçek Hayat Analojisi

Bir metin düzenleyicide (örneğin Word), yazılan yazılar belirli aralıklarla kaydedilir. “Geri Al (Undo)” dediğimizde, önceki kaydedilen duruma döneriz. Bu tam olarak Memento deseninin yaptığı şeydir.

->Kod Örneği

Bu örnekte bir Originator sınıfı durumu tutar, bir Memento sınıfı bu durumu saklar ve Caretaker sınıfı bu saklanan durumları yönetir.

class Memento {
    private String state;
    public Memento(String state){
        this.state = state;
    }
    public String getState(){
        return state;
    }
}
class Originator {
    private String state;
    public void setState(String state){
        this.state = state;
    }
    public String getState(){
        return state;
    }
    public Memento saveStateToMemento(){
        return new Memento(state);
    }
    public void getStateFromMemento(Memento memento){
        state = memento.getState();
    }
}
import java.util.ArrayList;
import java.util.List;
class CareTaker {
    private List<Memento> mementoList = new ArrayList<>();
    public void add(Memento state){
        mementoList.add(state);
    }
    public Memento get(int index){
        return mementoList.get(index);
    }
}
public class MementoPatternDemo {
    public static void main(String[] args) {
        Originator originator = new Originator();
        CareTaker careTaker = new CareTaker();
        originator.setState("Durum #1");
        originator.setState("Durum #2");
        careTaker.add(originator.saveStateToMemento());
        originator.setState("Durum #3");
        careTaker.add(originator.saveStateToMemento());
        originator.setState("Durum #4");
        System.out.println("Mevcut Durum: " + originator.getState());
        originator.getStateFromMemento(careTaker.get(0));
        System.out.println("İlk Geri Al: " + originator.getState());
        originator.getStateFromMemento(careTaker.get(1));
        System.out.println("İkinci Geri Al: " + originator.getState());
    }
}
//Çıktı
Mevcut Durum: Durum #4
İlk Geri Al: Durum #2
İkinci Geri Al: Durum #3

->Avantajları

Hatırlatıcı (Memento) tasarım deseni, bir nesnenin iç durumunu, nesnenin kendisi dışındaki diğer nesnelere doğrudan açığa vurmadan yakalamayı ve saklamayı mümkün kılar. Bu sayede, nesnenin daha önceki bir durumuna geri dönmek istendiğinde, saklanan bu durum bilgisi kullanılabilir. Özellikle geri alma (Undo) ve yineleme (Redo) gibi işlevselliklerin kolaylıkla uygulanmasını sağlayarak, kullanıcı etkileşimli uygulamalarda önemli bir rol oynar. Nesnenin iç yapısının korunmasını sağlayarak, dışarıdan müdahaleleri engeller ve nesnenin bütünlüğünü korur.

->Dezavantajları

Hatırlatıcı tasarım deseninin faydalarının yanı sıra, bazı potansiyel maliyetleri de göz önünde bulundurulmalıdır. Özellikle nesnelerin geçmiş durumlarını temsil eden çok sayıda hatırlatıcı nesnesinin bellekte saklanması gerektiği durumlarda, uygulamanın bellek tüketimi önemli ölçüde artabilir. Ayrıca, durumu büyük olan nesnelerin sık sık kaydedilmesi işlemi, kopyalama ve saklama süreçleri nedeniyle performansı olumsuz etkileyebilir. Bu nedenle, hatırlatıcı örüntüsü kullanılırken, saklanacak durum sayısı ve sıklığı ile bellek ve performans arasındaki dengenin dikkatli bir şekilde yönetilmesi önemlidir.

7.Observer (Gözlemci)

Observer deseni, bir nesnede meydana gelen değişiklikleri, bu nesneye bağlı olan diğer nesnelere otomatik olarak bildirmek için kullanılır. Bu desen yayıncı-abone (publisher-subscriber) modelini temel alır.

->Ne Zaman Kullanılır

  • Nesneler arası bire-çok (one-to-many) ilişkiler kurmak gerektiğinde.
  • Bir nesnede değişiklik olduğunda, buna bağlı diğer nesneler otomatik olarak bilgilendirilmeliyse.
  • Kullanıcı arayüzü gibi sık değişen bileşenler güncel tutulmak istendiğinde.

->Gerçek Hayat Analojisi

Bir haber bültenine abone olduğunuzu düşünün. Yeni bir haber çıktığında size e-posta gelir. Haberi gönderen sizi doğrudan aramaz, sadece sisteme haber ekler. Sistemde sizin e-posta adresiniz varsa, otomatik olarak bildirim alırsınız. Bu, Observer desenidir.

->Kod Örneği

Bu örnekte, bir Subject nesnesi gözlemcileri (observers) tutar. Her değişiklikte, bu gözlemciler bilgilendirilir.

interface Observer {
    void update(String message);
}
class EmailSubscriber implements Observer {
    private String name;
    public EmailSubscriber(String name) {
        this.name = name;
    }
    @Override
    public void update(String message) {
        System.out.println(name + " e-posta bildirimi aldı: " + message);
    }
}
interface Subject {
    void attach(Observer o);
    void detach(Observer o);
    void notifyObservers();
}
import java.util.ArrayList;
import java.util.List;
class NewsAgency implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private String news;
    public void setNews(String news) {
        this.news = news;
        notifyObservers();
    }
    @Override
    public void attach(Observer o) {
        observers.add(o);
    }
    @Override
    public void detach(Observer o) {
        observers.remove(o);
    }
    @Override
    public void notifyObservers() {
        for (Observer o : observers) {
            o.update(news);
        }
    }
}
public class ObserverPatternDemo {
    public static void main(String[] args) {
        NewsAgency agency = new NewsAgency();
        Observer sub1 = new EmailSubscriber("Ahmet");
        Observer sub2 = new EmailSubscriber("Zeynep");
        agency.attach(sub1);
        agency.attach(sub2);
        agency.setNews("Yeni haber: Observer pattern Java ile anlatıldı!");
        agency.detach(sub1);
        agency.setNews("Son dakika: Ahmet abonelikten ayrıldı.");
    }
}
//Çıktı
Ahmet e-posta bildirimi aldı: Yeni haber: Observer pattern Java ile anlatıldı!
Zeynep e-posta bildirimi aldı: Yeni haber: Observer pattern Java ile anlatıldı!
Zeynep e-posta bildirimi aldı: Son dakika: Ahmet abonelikten ayrıldı.

->Avantajları

Gözlemci (Observer) tasarım deseni, sistemdeki bileşenler arasında gevşek bir bağlantı kurarak, bir nesnenin (özne) durumundaki değişikliklerin, bu nesneye abone olan diğer nesnelere (gözlemciler) otomatik olarak bildirilmesini sağlar. Bu yapı, yeni gözlemcilerin sisteme kolayca eklenmesine olanak tanırken, özne ve gözlemciler arasındaki bağımlılığı en aza indirir. Ayrıca, öznenin durumundaki değişikliklerin anında gözlemcilere iletilmesi sayesinde, sistemde gerçek zamanlı bir senkronizasyon sağlanır. Bu özellik, özellikle kullanıcı arayüzleri, olay tabanlı sistemler ve dağıtık uygulamalar gibi alanlarda büyük avantajlar sunar.

->Dezavantajları

Gözlemci tasarım deseninin bazı potansiyel dezavantajları da bulunmaktadır. Özellikle bir özneye çok sayıda gözlemci abone olduğunda, öznenin durumundaki her değişiklik tüm gözlemcilere ayrı ayrı bildirim gönderilmesine neden olabilir. Bu durum, özellikle bildirim işlemlerinin maliyetli olduğu senaryolarda performans sorunlarına yol açabilir. Ek olarak, gözlemcilere yapılan bildirimlerin sırası genellikle garanti edilmez. Bazı durumlarda, gözlemcilerin bildirimleri belirli bir sırayla alması önemli olabilir, ancak gözlemci örüntüsünün doğası gereği bu sıralama kontrolü zor olabilir.

8.State (Durum)

State deseni, bir nesnenin iç durumuna bağlı olarak davranışını değiştirmesini sağlar. Sanki nesnenin sınıfı çalışma zamanında değiştiriliyormuş gibi davranır.

->Ne Zaman Kullanılır

  • Bir nesnenin davranışı, içindeki duruma göre değişiyorsa.
  • Uzun ve karmaşık if-else veya switch ifadeleri durumlara göre davranışı kontrol ediyorsa.
  • Her bir durumun kendi sınıfı ve davranışı olmasını istiyorsak

->Gerçek Hayat Analojisi

Bir turnike düşünün. Başlangıçta kilitli (Locked) durumdadır. Jeton atarsanız kilit açılır (Unlocked). Bu durumda tekrar jeton atarsanız, jetonu geri verir. Geçiş yaptıktan sonra tekrar kilitlenir. Her durum, turnikenin davranışını belirler.

->Kod Örneği

Bu örnekte bir Context sınıfı, mevcut durumu (State) temsil eder ve davranışları bu duruma göre delegelendirir.

interface State {
    void handle();
}
class LockedState implements State {
    @Override
    public void handle() {
        System.out.println("Turnike kilitli. Lütfen jeton atınız.");
    }
}
class UnlockedState implements State {
    @Override
    public void handle() {
        System.out.println("Turnike açık. Geçebilirsiniz.");
    }
}
class Turnstile {
    private State state;
    public Turnstile() {
        this.state = new LockedState(); // Başlangıç durumu
    }
    public void setState(State state) {
        this.state = state;
    }
    public void request() {
        state.handle();
    }
}
public class StatePatternDemo {
    public static void main(String[] args) {
        Turnstile turnstile = new Turnstile();
        // Başlangıç durumu: Locked
        turnstile.request();
        // Durumu değiştir: Unlocked
        turnstile.setState(new UnlockedState());
        turnstile.request();
    }
}
public class StatePatternDemo {
    public static void main(String[] args) {
        Turnstile turnstile = new Turnstile();
        // Başlangıç durumu: Locked
        turnstile.request();
        // Durumu değiştir: Unlocked
        turnstile.setState(new UnlockedState());
        turnstile.request();
    }
}
//Çıktı
Turnike kilitli. Lütfen jeton atınız.
Turnike açık. Geçebilirsiniz.

->Avantajları

Durum (State) tasarım deseni, bir nesnenin iç durumuna bağlı olarak davranışını değiştirmesine olanak tanır. Bu örüntü, nesnenin alabileceği her bir farklı durumu ayrı bir sınıf olarak tanımlar ve nesnenin mevcut durumunu temsil eden bir nesneye referans tutar. Nesnenin durumu değiştiğinde, bu referans farklı bir durum nesnesini işaret eder ve dolayısıyla nesnenin davranışları da değişir. Bu yaklaşım, özellikle karmaşık ve çok sayıda koşula bağlı davranış içeren uygulamalarda, iç içe geçmiş ve okunması zor if-else bloklarının ortadan kaldırılmasına yardımcı olur. Sonuç olarak, durum örüntüsü daha açık, organize ve anlaşılır bir yapı sunar, bu da kodun bakımını ve genişletilmesini kolaylaştırır.

->Dezavantajları

Durum tasarım deseninin sunduğu avantajların yanı sıra, bazı potansiyel zorlukları da bulunmaktadır. Özellikle bir nesnenin alabileceği farklı durum sayısının fazla olduğu senaryolarda, her bir durum için ayrı bir sınıf tanımlanması gerektiğinden, sistemdeki toplam sınıf sayısı önemli ölçüde artabilir. Bu durum, projenin genel karmaşıklığını artırabilir ve yönetimi zorlaştırabilir. Ayrıca, nesnenin farklı durumları arasındaki geçişlerin doğru ve tutarlı bir şekilde yönetilmesi, dikkatli bir planlama ve uygulama gerektirir. Yanlış yönetilen durum geçişleri, beklenmedik davranışlara ve hatalara yol açabilir.

9.Strategy (Strateji)

Strategy deseni, bir algoritmanın çeşitli versiyonlarını tanımlar ve bu algoritmaları birbirinin yerine kullanılabilir hâle getirir. Stratejiler, birbirlerinin yerine geçebilir ve çalışma zamanında dinamik olarak seçilebilir.

->Ne Zaman Kullanılır?

  • Aynı işi yapan ama farklı algoritmalara sahip sınıflar varsa,
  • Kodun içindeki koşullarla (if-else, switch) farklı davranışlar seçiliyorsa,
  • Davranışları çalışma zamanında değiştirmek gerekiyorsa.

->Gerçek Hayat Analojisi

Bir navigasyon uygulaması düşünün. Kullanıcıya “En kısa yol”, “Trafiği az olan yol” veya “Ücretli yolu kullanma” gibi seçenekler sunar. Bu seçeneklerin her biri farklı bir stratejidir.

->Kod Örneği

Bu örnekte bir bağlam (Context) sınıfı, strateji arayüzü üzerinden davranışı dışarıdan alır ve çalıştırır.

interface Strategy {
    int execute(int a, int b);
}
class Addition implements Strategy {
    @Override
    public int execute(int a, int b) {
        return a + b;
    }
}
class Subtraction implements Strategy {
    @Override
    public int execute(int a, int b) {
        return a - b;
    }
}
class Multiplication implements Strategy {
    @Override
    public int execute(int a, int b) {
        return a * b;
    }
}
class Context {
    private Strategy strategy;
    public Context(Strategy strategy) {
        this.strategy = strategy;
    }
    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }
    public int executeStrategy(int a, int b) {
        return strategy.execute(a, b);
    }
}
public class StrategyPatternDemo {
    public static void main(String[] args) {
        Context context = new Context(new Addition());
        System.out.println("Toplama: " + context.executeStrategy(10, 5));
        context.setStrategy(new Subtraction());
        System.out.println("Çıkarma: " + context.executeStrategy(10, 5));
        context.setStrategy(new Multiplication());
        System.out.println("Çarpma: " + context.executeStrategy(10, 5));
    }
}
//Çıktı
Toplama: 15
Çıkarma: 5
Çarpma: 50

->Avantajları

Strateji (Strategy) tasarım deseni, bir algoritma ailesini tanımlar, bu algoritmaların her birini ayrı bir sınıf içinde kapsüller ve bu algoritmaları birbirleriyle değiştirilebilir hale getirir. Bu sayede, farklı algoritmalar sistemin diğer kısımlarını etkilemeden bağımsız bir şekilde geliştirilebilir ve yönetilebilir. Bu durum, kodun bakımını önemli ölçüde kolaylaştırır, çünkü bir algoritmadaki değişiklik diğer algoritmaları veya algoritmayı kullanan istemci kodunu etkilemez. Dahası, hangi algoritmanın kullanılacağı çalışma zamanında dinamik olarak belirlenebilir veya değiştirilebilir, bu da sisteme büyük bir esneklik kazandırır. Örneğin, farklı sıralama algoritmaları veya farklı ödeme yöntemleri strateji örüntüsü kullanılarak kolayca uygulanabilir ve değiştirilebilir.

->Dezavantajları

Strateji tasarım deseninin sunduğu esneklik ve bakım kolaylığına karşın, bazı potansiyel dezavantajları da bulunmaktadır. Her bir farklı algoritma için ayrı bir sınıf oluşturulması gerektiğinden, sistemdeki toplam sınıf sayısı artabilir. Bu durum, özellikle çok sayıda farklı stratejinin gerektiği durumlarda projenin karmaşıklığını artırabilir. Ayrıca, hangi stratejinin ne zaman ve nasıl kullanılacağına karar veren istemci kodunun, mevcut stratejiler hakkında bilgi sahibi olması ve uygun olanı seçip ayarlaması gerekebilir. Bu durum, istemci ile strateji sınıfları arasında bir miktar bağımlılık oluşturabilir.

10.Template Method (Şablon Metot)

Template Method deseni, bir algoritmanın iskeletini (şablonunu) üst sınıfta tanımlar ve bazı adımlarını alt sınıfların özelleştirmesine izin verir. Böylece, algoritmanın yapısı korunur ama bazı adımlar alt sınıflarda değiştirilebilir.

 ->Ne Zaman Kullanılır?

  • Bir algoritmanın adımları sabit ama bazı adımları özelleştirilebilir olduğunda.
  • Kod tekrarını azaltmak ve algoritma akışını merkezi bir yerde tanımlamak istendiğinde.

->Gerçek Hayat Anolojisi

Bir çay ya da kahve hazırlama süreci düşünün:

  1. Su kaynat
  2. İçeriği demle
  3. Bardaklara dök
  4. İsteğe bağlı olarak şeker/süt ekle

Bu adımların sırası sabittir (template), ancak “demleme” adımı içeriğe göre değişebilir (çay ya da kahve).

->Kod Örneği

Bu örnekte içecek hazırlama süreci genel bir şablon olarak tanımlanır ve alt sınıflar bazı adımları özelleştirir.

abstract class CaffeineBeverage {
    // Şablon metot
    public final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }
    abstract void brew();
    abstract void addCondiments();
    void boilWater() {
        System.out.println("Suyu kaynat");
    }
    void pourInCup() {
        System.out.println("Bardağa dök");
    }
    // Hook method (isteğe bağlı adım)
    boolean customerWantsCondiments() {
        return true;
    }
}
class Tea extends CaffeineBeverage {
    @Override
    void brew() {
        System.out.println("Çayı demle");
    }
    @Override
    void addCondiments() {
        System.out.println("Limon ekle");
    }
}
class Coffee extends CaffeineBeverage {
    @Override
    void brew() {
        System.out.println("Kahveyi demle");
    }
    @Override
    void addCondiments() {
        System.out.println("Şeker ve süt ekle");
    }
    // Opsiyonel adımı atlamak için override edilebilir
    @Override
    boolean customerWantsCondiments() {
        return false; // Sade kahve
    }
}
public class TemplateMethodDemo {
    public static void main(String[] args) {
        CaffeineBeverage tea = new Tea();
        System.out.println("Çay hazırlanıyor:");
        tea.prepareRecipe();
        System.out.println("\nKahve hazırlanıyor:");
        CaffeineBeverage coffee = new Coffee();
        coffee.prepareRecipe();
    }
}
//Çıktı
Çay hazırlanıyor:
Suyu kaynat
Çayı demle
Bardağa dök
Limon ekle
Kahve hazırlanıyor:
Suyu kaynat
Kahveyi demle
Bardağa dök

->Avantajları

Şablon Metot (Template Method) tasarım deseni, bir algoritmanın iskeletini (şablonunu) bir üst sınıfta tanımlar ve algoritmanın belirli adımlarını alt sınıflara bırakır. Bu sayede, algoritmanın genel yapısı korunurken, alt sınıflar bu adımların özel uygulamalarını sağlayabilir. Bu yaklaşım, kod tekrarını önemli ölçüde azaltır, çünkü algoritmanın ortak kısımları tek bir yerde tanımlanır ve farklı alt sınıflar tarafından tekrar tekrar kullanılabilir. Ayrıca, şablon metot içinde tanımlanan "hook metotları" gibi mekanizmalar sayesinde, alt sınıflara algoritmanın belirli noktalarına müdahale etme ve davranışını özelleştirme esnekliği sunulur. Bu, algoritmanın genel yapısını bozmadan farklı ihtiyaçlara uyarlanabilmesini sağlar.

->Dezavantajları

Şablon Metot tasarım deseninin faydalarının yanı sıra, bazı sınırlamaları da bulunmaktadır. Alt sınıfların, algoritmanın hangi adımlarını uygulayacaklarını ve bu adımların algoritmanın genel akışıyla nasıl etkileşime gireceğini anlamaları gerekir. Bu, alt sınıflar üzerinde bir miktar bilgi yükü oluşturabilir. Ayrıca, şablon metot içindeki tüm adımlar soyut metotlar olarak tanımlanmazsa, yani bazı adımların somut bir uygulaması üst sınıfta verilirse, alt sınıfların bu adımları özelleştirme esnekliği sınırlı kalabilir. Bu durumda, algoritmanın belirli kısımlarını değiştirmek isteyen alt sınıflar için kısıtlayıcı bir durum ortaya çıkabilir.

11. Visitor (Ziyaretçi)

Visitor deseni, bir nesne yapısındaki (örneğin bir ağaç, koleksiyon veya dosya sistemi gibi) öğelerin üzerine yeni işlemler tanımlamak için kullanılır. Bu işlemler, öğelerin sınıflarını değiştirmeye gerek kalmadan gerçekleştirilir.

->Ne Zaman Kullanılır?

  • Nesnelerin yapısını bozmadan yeni işlemler eklemek istendiğinde.
  • Nesne sınıfları kapalı, ama onlara uygulanacak işlemler açık olmalıysa (Open/Closed Principle).
  • Aynı yapıya sahip nesneler üzerinde farklı türde işlemler gerçekleştirilmek isteniyorsa.

->Gerçek Hayat Analojisi

Gümrükte bir bavul kontrolü düşünün. Her bavulun tipi farklı olabilir (çanta, valiz, koli), fakat gümrük görevlisi (visitor) her tür için farklı bir işlem uygular.

Valizi açar, çantayı elle kontrol eder, koliyi x-ray’den geçirir.

Görevli, bavulun türünü bilmeden “ziyaret eder” ve işlem yapar.

->Kod Örneği

Bu örnekte, çeşitli Shape (şekil) türleri bir XML dosyasına dışa aktarılır.

Şekillerin sınıfları değiştirilmeden, dışa aktarma işlemi Visitor sınıfı aracılığıyla gerçekleştirilir.

// Element Arayüzü
interface Shape {
    void accept(Visitor visitor);
}
// ConcreteElement Sınıfları
class Dot implements Shape {
    int id;
    int x, y;
    public Dot(int id, int x, int y) {
        this.id = id;
        this.x = x;
        this.y = y;
    }
    public void accept(Visitor visitor) {
        visitor.visitDot(this);
    }
}
class Circle implements Shape {
    int id;
    int x, y, radius;
    public Circle(int id, int x, int y, int radius) {
        this.id = id;
        this.x = x;
        this.y = y;
        this.radius = radius;
    }
    public void accept(Visitor visitor) {
        visitor.visitCircle(this);
    }
}
// Visitor Arayüzü
interface Visitor {
    void visitDot(Dot dot);
    void visitCircle(Circle circle);
}
// ConcreteVisitor
class XMLExportVisitor implements Visitor {
    public void visitDot(Dot dot) {
        System.out.println("<dot id='" + dot.id + "' x='" + dot.x + "' y='" + dot.y + "' />");
    }
    public void visitCircle(Circle circle) {
        System.out.println("<circle id='" + circle.id + "' radius='" + circle.radius + "' />");
    }
}
// Kullanım
public class VisitorDemo {
    public static void main(String[] args) {
        List<Shape> shapes = List.of(
            new Dot(1, 10, 20),
            new Circle(2, 15, 25, 5)
        );
        Visitor exportVisitor = new XMLExportVisitor();
        for (Shape shape : shapes) {
            shape.accept(exportVisitor); // Her şekil kendini ziyaretçiye teslim eder
        }
    }
}
//Çıktı
<dot id='1' x='10' y='20' />
<circle id='2' radius='5' />

->Avantajları

Ziyaretçi (Visitor) tasarım deseni, bir nesne yapısındaki (örneğin bir ağaç veya bir koleksiyon) elemanlar üzerinde gerçekleştirilecek yeni işlemleri, bu elemanların sınıflarında herhangi bir değişiklik yapmaya gerek kalmadan tanımlamayı ve eklemeyi mümkün kılar. Bu sayede, sisteme yeni işlevsellikler eklemek gerektiğinde, mevcut nesne sınıfları korunur ve değişiklikler merkezi olarak ziyaretçi sınıfları üzerinden yönetilir. Ayrıca, ziyaretçi örüntüsü, farklı türdeki nesnelerden oluşan bir yapı üzerinde aynı mantıksal işlemi gerçekleştirmeyi kolaylaştırır. Her bir nesne türü için ayrı bir ziyaretçi metodu tanımlanarak, işlem tüm yapıya tutarlı bir şekilde uygulanabilir.

->Dezavantajları

Ziyaretçi tasarım deseninin sunduğu avantajların yanı sıra, bazı önemli dezavantajları da bulunmaktadır. Nesne yapısına yeni bir eleman (element) türü eklendiğinde, bu yeni elemanı işleyebilmek için mevcut tüm ziyaretçi sınıflarının güncellenmesi gerekliliği ortaya çıkar. Bu durum, özellikle sık sık yeni elemanların eklendiği sistemlerde bakım maliyetini artırabilir. Ayrıca, ziyaretçi ile ziyaret edilen eleman arasındaki çift yönlü etkileşim mekanizmasının başlangıçta doğru bir şekilde kurulması ve anlaşılması karmaşık olabilir. Ziyaretçinin eleman üzerinde işlem yapabilmesi ve elemanın da ziyaretçiyi kabul edebilmesi için gerekli olan arayüzlerin ve metotların tasarımı özen gerektirir.

Kaynakça

"Behavioral Design Patterns," Refactoring Guru. Erişim Tarihi: 2025-04-05. https://refactoring.guru/design-patterns/behavioral-patterns.

Freeman, Eric, Elisabeth Robson, Bert Bates, and Kathy Sierra. Head First Design Patterns. O'Reilly Media, 2004.

Gamma, Erich, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994.

Sen de Değerlendir!

0 Değerlendirme

Blog İşlemleri

KÜRE'ye Sor