Design Patterns — Behavioral
Bir önceki yazı için Design Patterns — Structural
Tasarım kalıplarını tanıma ve öğrenme sürecinin son basamağı ve aynı zamanda serinin sonu niteliğinde olan Behavioral Design Patterns (Davranışsal Tasarım Kalıpları) sıra geldi. Bu süreçte tasarım kalıpları için kendi adıma güzel bir türkçe kaynak oluşturduğumu düşünüyorum.
Yine bu yazı ile birlikte her bir tasarım kalıbını tecrübe ettiğim kod örneklerini de github’a yükledim.
Behavioral Design Patterns nesnelerin çalışma zamanında sergiledikleri davranışları değiştirmek için kullanılan tasarım kalıplarından oluşur.
Gof kitabına göre Behavioral Design Patterns 11 adet tasarım kalıplarından oluşur;
- Observer Pattern
- Chain of Responsibility Pattern
- State Pattern
- Mediator Pattern
- Command Pattern
- Iterator Pattern
- Interpreter Pattern
- Strategy Pattern
- Template Method Pattern
- Visitor Pattern
- Memento Pattern
Observer Pattern
Birbirine one-to-many olarak bağlı olan nesnelerin birinde (one) gerçekleşen değişiklikleri ona bağlı olan diğer nesnelere (many) durumunu haberdar etmek için kullanılır. Yani bir nesneye abone olunuyor (subscribe) ve bu nesne üzerinde gerçekleşen değişimler abone olan diğer nesnelere; hey benim durumum değişti bi bak istersen! diye haberdar ediyor 😊
Ne Zaman Kullanmalıyız
- Bir nesnede meydana gelen değişimler ve ona bağlı olan diğer nesneleri etkilemesini istiyorsak
- Değişimini izlemek istediğimiz nesnelerle çalışmak istiyorsak
Observer Pattern aynı zamanda loosely-coupled ilişkisi vardır. Yani iki obje birbirleri ile ilişkilidir ama birbirleri hakkında çok az şey bilirler
Kullanım Sıklığı
- Yüksek -> %100
UML Diagram
- Subject: Takip edilecek olan nesne. Durumu değiştiğinde gözlemcilere haber verir.
- Observer: Abstract gözlemci sınıfımız. Birden fala gözlemci tek bir çatıda toplanmasını ve takip edilmesini sağlar
- ConcreteObserver: Observer’ı uygulayan gerçek nesnemiz.
Chain of Responsibility Pattern
Sisteme gönderilen bir komutun (command) birbirleri ile loosley-coupled şekilde bağlı olan nesneler arasında gezdirilmesi ve asıl sorumlu tarafından ele alınması (handle) ancak ele almadığı durumlarda komutu zincirdeki bir sonraki nesneye iletmesi için kullanılan tasarım kalıbıdır. Kısacası; bir işlemi yapabilecek birden fazla sınıftan hangisinin yapabileceğine karar veren bir tasarım kalıbıdır.
Ne Zaman Kullanmalıyız
- If-else, switch yapılarının çok fazla olduğu kod karmaşasını engellemek istiyorsak
- Bir işlemi sırasıyla ele almak istiyorsak
- İsteği oluşturan (request) ve bu isteği ele alan (handle) arasında loosely-coupled bir ilişki kurmak istiyorsak
Kullanım Sıklığı
- Orta düşük -> %40
UML Diagram
- Diktat çeken Handler ile ConcreteHandler arasında Aggregate söz konusu. Bunun olması çok doğal çünkü zincirdeki bir sonraki elemana bu referans ile aktarım yapılacak.
- Handler: Çatı sınıfımız. Abstract ya da interface olabilir. Kendisinden türeyen sınıflar için arayüz niteliğindedir.
- ConcreteHandler: Sorumlu olduğu işi ele alan gerçek nesnemiz. Gerekirse işi zincirin bir sonraki halkasındaki nesneye aktarır.
- Client: talebi gönderen
Chain of Responsibility Pattern Kod Örneği
Mediator Pattern
Nesnelerin yönetimini ve aralarındaki iletişimi merkezi bir yerden sağlanması ve yönetmesi için kullanılan tasarım kalıbıdır. Nesneler arasında loosley-coupled bir şekilde ilişki sağlar ve bir sınıfı yönetici sınıf olarak diğer sınıfların koordineli çalışması için sorumlu tutar.
Ne Zaman Kullanmalıyız
- Sınıflar arasındaki bağımlığı azaltmak ve aralarındaki iletişimi kolaylaştırmak istiyorsak
- Anlaşılması zor ve yönetilmesi karmaşık olan nesnelerle çalışıyorsak
Kullanım Sıklığı
- Orta yüksek -> %40
UML Diagram
- Yukarıda görüldüğü gibi Colleague’den Mediator’a doğru, Concorete Mediator’dan ConcreteColleague sınıflarına doğru tek yönlü (Asscoation) ilişki vardır.
- Mediator: Çatı sınıfımız. Abstract/Interface olabilir. Diğer sınıfları yönetecek sorumlu sınıfımız.
- Colleague: Abstract ya da Interface olabilir. Birden fazla Colleague üretmemiz için arayüz sağlar.
Command Pattern (Komut Kalıbı)
Bir isteği bir nesne olarak kapsülleyerek (encapsulation) bir eylemi gerçekleştirmek ya da daha sonraki bir zamanda bir olayı tetiklemek için kullanılan tasarım kalıbıdır. Kısacası; yapılmak istenen işi nesneye dönüştürerek alıcı nesnesi tarafından işlemin ele alınmasını sağlar
Ne Zaman Kullanmalıyız
- Bir isteği nesne olarak kullanmak istiyorsak
- Nesne üzerinde bir işlemin nasıl yapılacağını bilmediğimiz ya da kullanılmak istenen nesneyi tanımadığımız durumlarda
- İşlemi tetikleyen nesneler ile işlemi ele alan nesneleri birbirinden ayırmak istiyorsak
Kullanım Sıklığı
- Orta yüksek -> %40
UML Diagram
- 4 terim her zaman birbirleri ile ilişkilidir. Command, Invoker, Reveiver, Client
- Invoker ile ICommand arasında Aggregation söz konusudur.
Aggregation sınıflar arasında parçası olma anlamı vardır. Yani bir sınıfın bir parçası olma gibi. ICommand Invoker nesnesinin bir parçasıdır ama ICommand yok olduğunda Receiver yok olma zorunluluğu yoktur aralarında zayıf bir ilişki vardır
- ICommand: Komutların ortak türe sahip olması için tanımlanan arayüz
- Receiver: Asıl işlemi yapacak sınıf.
- Invoker: Komutları tetikleyecek olan sınıf. Ancak hangi komut nesnesini çağıracağını bilmez.
- Client: Invoker sınıfını çağırır.
Iterator Pattern
Nesne koleksiyonlarının (list, queue, stack…) elemanlarını belirlenen bir kurala göre sırasıyla erişebilmeyi sağlayan tasarım kalıbıdır. Bu tasarım kalıbında Iterator ve Iteratable yani Enumerator ve Enumerable parçalarından oluşuyor. Iterator tekrarlayıcı, Iteratable tekrarlanan anlamına gelir. Nesne koleksiyonundaki her bir elemana for; ya da foreach; ile erişerek aslında bu patterni sık sık kullanıyoruz.
Ne Zaman Kullanmalıyız
- Nesne koleksiyonunda yer alan elemanları sırasıyla uygulamadan soyutlamak istiyorsak
- Koleksiyon nesnesinin belirli bir kurala göre içindeki elemanları sırasıyla erişmek istiyorsak
Kullanım Sıklığı
- Yüksek -> %100
UML Diagram
- Iterator: Nesne koleksiyonundaki elemanlara erişebilmek için bir arayüz sağlar.
- Aggreagate: Tekrarlayıcı nesnelerin yaratılması için arayüz sağlar. Koleksiyon nesnemizdeki iterate edilecek elemanları temsil eder.
Interpreter Pattern
Belirli bir mantıktaki metinlerin nasıl yorumlanacağını belirleyen bir tasarım kalıbıdır. Belirli mantıktaki metin derken; cryptolanmış metin belgeleri, binary dönüştürülmüş video ya da image’ler, encode edilmiş content’ler gibi işlemler. Kural işletme motorları (Rule Engine) söz konusu olduğunda bu tasarım kalıbı kullanılır.
Ne Zaman Kullanmalıyız
- Metin üzerinde belirli bir mantıkta yorumlama ve çevirme işlemlerini bir bütün olarak yapmak istiyorsak
- Metindeki verilerin sayısal ve mantıksal olarak yorumlanmasını istiyorsak
Kullanım Sıklığı
- Düşük -> %20
- Bunun sebebi belirli bir kural gerektiren grammer yapılarında kullanıldığı için kısıtılı bir kullanımı vardır.
UML Diagram
- Context: Yorumlanacak içerik sınıfı.
- AbstractExpression: Abstract/Interface olabilir. Context’in yorumlanması için iş mantığını üstlenir (Business Logic)
- TerminalExpression: Belirlenen bir mantığa göre işletilen operand barındırır.
- NonTerminalExpression: Bazen iki Terminal’ı “or” ya da “and” operandları ile bağlayabilir. İşletme önceliği bu sınıftadır.
Interpreter Pattern Kod Örneği
State Pattern (Durum Kalıbı)
Bir nesnenin içsel durumunda (internal-state) meydana gelen değişim sonrası çalışma zamanında dinamik olarak farklı davranışları sergileyebilmesini sağlayan tasarım kalıbıdır. State Pattern aslında Workflow yapısındaki State Machine kavramının denk geliyor. Yani nesnenin durumunun değiştiğinde farklı davranışların sergilemesi, nesneye ait fonksiyonların tetiklenmesi ve bunlara bağlı olarak geçişlerin (transition) sağlanması anlamına gelir.
Ne Zaman Kullanmalıyız
- Bir durum değiştiğinde değişen davranışları if-else, switch gibi yapılarla oluşacak kod karmaşıklığın önüne geçmesini istiyorsak
- Bir nesnenin iç durumunda değişiklik meydana geldiğinde dinamik olarak davranış sergilemesini istiyorsak
Kullanım Sıklığı
- Medium -> %60
UML Diagram
- Context: internal state bilgisini tutan sınıfımız
- Context ile State arasında bir Aggregaion söz konusu. Çünkü Context üzerinde her bir request için durum değişikliği olacak ve bunu da State referansı üzerinden yapacak
- State: Context durumunu netleştiren ve ortak bir arayüz sağlayan sınıfımız
- ConcreteContext: State arayüzünü uygulayan ve durumu ele alan davranışlar uygular.
Strategy Pattern (Strateji Kalıbı)
Algoritma kümesinden hangi algoritmanın kullanılacağını çalışma zamanında belirleyen tasarım kalıbıdır. Algoritma kümesindeki her bir algoritma birbirleri yerine kullanılabilir.
Ne Zaman Kullanmalıyız
- Bir işi yapabilecek birden fazla algoritma varsa
- Nesnelerin çalışma zamanında algoritmalara göre davranışlarını değiştirmek istiyorsak
- Yapılmak istenen bir işi birden fazla yöntemle yapmak istiyorsak
- If-else, switch gibi yapılarla kod karmaşıklığının önüne geçmek istiyorsak
Bu sayede esnek bir yapı sağlanmış olur. Yani sınıfta değişiklik yapmaya gerek olmadan özellikler extend edilebilir. Buda bize Open/Closed Princible hatırlatır.
Kullanım sıklığı
- Orta Yüksek -> %80
UML Diagram
- Context ve Strategy arasında Aggregation söz konusu. Bu olmalı ki Context belirlenen Strategy referansı üzerinden işlemini yapması gerekiyor.
- Strategy: Interface/Abstract yapıda olabilir. Birden fazla algoritmalar için çatı görevini üstlenir.
- ConcreteStrategy: İlgili Algoritmayı gerçekleştiren somut sınıflarımız.
- Context: Strategy sınıfı ile arasında loosley-coupled ilişkisi olan sınıfımız
Template Method Pattern
Bir işlem için gerekli olan adımları soyutlayarak bir şablon metot ile algoritmaların nasıl çalışacağını belirleyen ve kalıtım ile alt sınıfların bu soyut adımları kendi bünyesinde uygulayarak algoritmaların çalışmasını sağlayan tasarım kalıbıdır. Template Method ile Strategy arasında güçlü benzerlik vardır. İkisi de genişletilebilirdik ve özelleştirme için tasarlanmıştır.
Ne Zaman Kullanmalıyız
- Sıralı işlemlerden bazılarını soyutlamak istiyorsak
- Algoritmaların değişen kısımlarını altı sınıfların ele almasını istiyorsak
- Algoritma iskeletinde yapılacak olan değişikliklerin merkezi bir yer yerden yapılmasını istiyorsak
Kullanım Sıklığı
- Medium -> %60
UML Diagram
- AbstractClass: Soyut adımları belirlediğimiz ve bu adımlar için Template Method barındıran sınıfımız.
- ConcreteClass: Soyut metotları uygulayacak somut sınıflarımız.
Template Method Pattern Kod Örneği
Visitor Pattern
Hiyerarşik yapıda ve farklı tipte olan nesnelerin mevcut yapılarını değiştirmeden işlem yapabilmek amacıyla kullanılır. İşlemi ziyaretçi (visitor) nesneler yapar. Ziyaretçi nesneler sayesinde yeni metotlar tanımlanabilir ve ilgili nesneler bu ziyaretçi nesneye parametre olarak aktarılıp işlem yapılır.
Ne Zaman Kullanmalıyız
- Yapıya sıklıkla yeni işlemler eklemek istiyorsak
- Mevcut yapıyı değiştirmeden ek işlemler yapmak istiyorsak
- Yapılacak işlemleri merkezi bir nesneden yönetmek istiyorsak
Kullanım Sıklığı
- Düşük -> %20
UML Diagram
- Visitor: Abstract/Interface yapıda tasarlanabilir. Ziyaret edilecek nesnelerin metot gövdelerini barındırır.
- ConcreteVisitor: Ziyaret edilecek nesneler ile işlemlerin ele alındığı sınıf.
- Element: Ziyaret edilecek sınıf.
- ConcreteElement: Ziyaretçi nesnemiz ile haberleşen sınıf.
Memento Pattern
Nesnelerin hallerini tutan ve farklı nesne halleri arasında geçiş yapmayı sağlayan ve bir nesneyi önceki duruma geri yükleme kabiliyeti kazandıran tasarım kalıbıdır.
Ne Zaman Kullanmalıyız
- Nesne durumunun anlık görüntüsü (snapshot) almak istiyorsak
- Nesnenin bir önceki durumuna geri yüklenebilmesini istiyorsak
Kullanım Sıklığı
- Düşük -> %20
UML Diagram
- Originator: Sınıfımızın eski ve yeni hallerini tutacak olan nesne.
- Memento: Originator sınıfındaki istediğimiz alanları tutacak olan nesne.
- Caretaker: Geri dönüş adımlarını Memento tipinde tutan nesne.
Bir yazının sonuna daha geldik. Behavioral Design Patterns hakkında elde ettiğim bilgiyi paylaşmaya çalıştım. Umarım faydalı olmuştur.
Sağlıkla kalın!