Design Pattern Nedir?

Mehmed Emre AKDİN
SDTR
9 min readFeb 11, 2021

--

Herkese yeniden merhaba,

Umarım günleriniz güzel geçiyordur ve güzel geçmeye devam eder. Bugün farklı bir içerikle karşınızda olacağım. Hemen hemen her yazılımcının her yerde farklı şekillerde karşısına çıkan “Design Pattern” konusundan bahsedeceğim. Bu yazım “Design Pattern” ‘lere giriş niteliğinde olacaktır.

Tarihçeye Değinmeden Olmaz!

Desen kavramı Christopher Alexander tarafından mimari bir konsept olarak ortaya çıkmıştır. Christopher Alexander kentsel tasarım , yazılım , sosyoloji ve daha birçok alanda mimarinin ötesindeki alanları etkilemiştir. Bu alanlardan bizi ilgilendiren yazılım kısmına geçiş Kent Beck ve Ward Cunningham’ın bu tasarım desenlerini programlamaya uygulamaya başlamasıyla başlamıştır. Sonraki yıllarda Beck , Cunningham ve diğerlerinin bu çalışmayı takip etmesiyle tasarım kalıpları yazılım alanında popülerlik kazanmaya başlamıştır. 1994'te Erich Gamma, Richard Helm, Ralph Johnson ve John Vlissides tarafından yayınlanan “Design Patterns: Elements of Reusable Object-Oriented Software” kitabı tasarım desenlerinin yazılımda kullanılmasında dönüm noktası olmuştur. Bu kitabı yazanlar “Gang of Four” takma isimiyle adlandırılıyor. Genelde bu takma adın yerine “GoF” kısaltması kullanıldığını görebilirsiniz.

Design Pattern(Tasarım Deseni) Nedir?

“Desing Pattern” ’ler yazılım projelerinde bir çoğumuzun sıklıkla karşılaştığı problemlere çözüm olarak sunulan esnek , tekrarlanabilir ve bazı durumlarda beraber uygulanabilir kalıplardır. “Desing Pattern” ’ler, doğrudan koda dönüştürülebilen bir kodun eksiksiz bir yansıması değildir, daha ziyade, çeşitli şekillerde kullanılabilmesi için sorunun nasıl çözüleceğine ilişkin bir açıklama veya örnek niteliğindedir.

“Desing Pattern”, genel bir tasarım probleminin çözümü için uyarlanmış, nesnelerin ve sınıfların etkileşiminin bir açıklamasıdır. Bu yüzden bu kavram nesneye yönelimli programlama ile beraber adını duyurmuştur. Çözüm yolu ve tekniğinden bahsedildiği için bir algoritma olarak algılayabilirsiniz fakat algoritmalar daha çok hesaplama görevini çözdükleri için bir tasarım deseni değildir.

Neden Design Pattern’leri Kullanalım?

Etkili “Design Pattern” ‘ler bizlere birçok yazılımcı tarafından test edilmiş ve onaylanmış geliştirme modelleri sağlayarak yazılımı geliştirme ve tasarlama sürecini hızlandırır. Kodumuzun tasarım aşamasında gözümüzden kaçabilecek ve ileride büyük problemler oluşturabilecek küçük sorunların önlenmesine olanak tanır. Pattern’lerin tekrarlı olarak kullanılması ve bazı yazım standartlarına uygun olarak geliştirilmesi kodun okunabilirliğini ve anlaşılabilirliğini büyük ölçüde arttıracaktır. Bu sayede yazım standartında ortak bir dil oluştururuz. Bu “Design Pattern” ‘ler belirli senaryolara yönelik olarak geliştirildiğinden aralarından probleme uygun olanını bulmak ve uygulamak oldukça kolaydır. Ayrıca bu kalıpları öğrenmek ve anlamaya çalışmak, deneyimsiz geliştiricilerin yazılım tasarımını kolay ve hızlı bir şekilde öğrenmesine yardımcı olur.

Design Pattern Türleri

Yukarıda bahsetmiş olduğum “Design Patterns: Elements of Reusable Object-Oriented Software” kitabına göre üç kategoride sınıflandırılabilen 23 “Design Pattern” vardır. Bu üç kategoriye kısaca değineceğim ve her kategori için birer örnek vererek işin mantığını anlatmaya çalışacağım.

1-) Creational Designs Patterns

Bu tasarım kalıpları, nesne oluşturma mekanizmalarıyla ilgilenirler. Durumlara uygun nesneler oluştururlar. Belirli bir kullanım durumu için hangi nesnelerin oluşturulması gerektiğine karar vermede esneklik sağlarlar. Kısaca mevcut kodun esnekliğini ve yeniden kullanımını artıran çeşitli nesne oluşturma mekanizmaları sağlarlar. Aşağıda “Creational Designs Patterns” ‘ler verilmiştir:

  • Factory Method Pattern
  • Abstract Factory Pattern
  • Builder Pattern
  • Prototype Pattern
  • Singleton Pattern

Singleton Pattern

Bu pattern bir sınfın yalnızca bir nesne örneğine sahip olmasına ve bu örneğe genel bir erişim noktasından erişilmesine olanak tanır. Bir sınıf nesnesinin tek bir örneğinin olması bazı paylaşılan kaynaklara erişimin kontrollü sağlanması gerektiğinde işimize yarayacaktır. Genelde veri tabanı nesnesinin singleton olarak kullanıldığını görebilirsiniz. Bazı ASP.NET uygulamalarında Repository pattern olarak kullanılan ve CRUD işlemlerimizi yaptığımız sınıfın RepositoryBase isimli başka bir sınıfı kalıtım yoluyla aldığını ve RepositoryBase sınıfında sadece veri tabanı nesnesinin tekil üretilmesi için singleton olarak dizayn edildiğini görmüş olabilirsiniz.

Bir örnekle patterni netleştirelim:

Bir veri tabanı nesnemiz olsun ve uygulama boyunca tek bir nesne üzerinden işlem yapmak isteyelim:

public  class Singleton
{
private static DataBaseContext database;
private static object _lockSync = new object();
private Singleton()
{

}
public static DataBaseContext create_ınstance()
{

lock(_lockSync){
if(database == null){ database = new DataBaseContext();
}
}
return database;
}
}

Örneği dikkatli bir şekilde incelersek global bir static değişken üzerinde nesnemizi tutduğumuzu ve metod içerisinde bu değişkene kontrollü bir şekilde erişim sağladığımızı görebiliriz.

static void Main(string[] args)
{
DataBaseContext singleInstance1 = Singleton.create_ınstance();//Çeşitli işlemlerDataBaseContext singleInstance2 = Singleton.create_ınstance();//Çeşitli işlemler if (singlInstance1 == singlInstance2)
{
Console.WriteLine("İşlemler Tek Bir Nesne Örneği Üzerinden Gerçekleştirilmiştir.");
}
}

Sonuç:

İşlemler Tek Bir Nesne Örneği  Üzerinden Gerçekleştirilmiştir.

Nesnenin ilk oluşturulduğu sırada threadleri senkronize etmek için lock(_lockSync) kullanılarak ayrıca “Thread- Safe Singleton” örneği de gerçekleştirmiş olduk. Ek olarak Singleton sınıftan kalıtım almak istiyorsak constructor protected olarak işaretlenmelidir.

2-)Structural Design Patterns

Bu tasarım kalıpları sınıflarımız ve nesnelerimiz arasındaki ilişkileri belirlememize yardımcı olur. Buna bağlı olarak sınıflar arası hiyerarşinin nasıl olması gerektiği, hangi nesneleri içermesi gerektiği ve arayüzlerin nasıl kullanıldığı hakkında bilgiler verir. Sonuç olarak daha gelişmiş yapıları en iyi şekilde nasıl elde edebileceğimiz sorununu çözer. Aşağıda “Structural Design Patterns” ‘ler verilmiştir:

  • Adapter Pattern
  • Bridge Pattern
  • Composite Pattern
  • Decorator Pattern
  • Facade Pattern
  • Flyweight Pattern
  • Proxy Pattern

Adapter Pattern

Bu pattern uyumsuz arayüzlere sahip nesnelerin birlikte iş yapmasına izin verir. Bir nesnenin arayüzünü başka bir nesnenin anlayabilmesi için dönüşüm görevini üstlenen adapter kullanılır. Adapter bu işi mevcut nesnelerden biriyle uyumlu bir arayüz implemente ederek yapar. Bunu örnek üzerinden size anlatmaya çalışacağım.

Bir derneğimiz olduğunu düşünelim. Bu dernek sokakta yaşayan insanlar için bağış toplayan bir dernek olsun. Bu hayırsever derneğimiz bağışlarını sokak üzerinde oluşturdukları otomatlar aracılığı ile alıyor olsunlar. Basitçe aşağıdaki şekilde bir ödeme sistemi düşünelim:

Kod:

public interface Cash
{
void pay(int cash);
}
public class CashDonate : Cash
{
public void pay(int cash)
{
Console.WriteLine(cash + " TL Bağışınız İçin Teşekkür Ederiz!");
}

}

Nakit bağış yapalım: 💰

static void Main(string[] args)
{
Cash donate1 = new CashDonate();
donate1.pay(500);
}

Sonuç:

500 TL Bağışınız İçin Teşekkür Ederiz!

Günler ilerledikçe hayırsever insan sayısı artıyor ve derneğimize yardım eden insanlar kredi kartı ile ödeme yapabilmek istiyorlar. Ardından derneğimiz hızlıca bir karar alıyor. Kullanıcı arayüzünü değiştirmeden kredi kartı ile ödemeyi sisteme eklemek istiyor. Hemen ekleyelim:

Bunun için adapter sınıfını oluşturacağız. İki ayrı arayüzü tek bir arayüz ile sarmalayıp tek bir arayüzden işlem yürüteceğiz. Anlattıklarımızı uml diyagramı üzerinden inceliyelim.

“CreditAdapter” sınıfı yalnızca “Cash” interface’ini implement’e ediyor. Çünkü “Cash” interface’ine adapte edeceğiz. Ardından adapte edilecek arayüzün örneğini referansı olarak alıyor. Yani “Credit” arayüzünü.

Kod:

public interface Cash
{
void pay(int cash);
}
public interface Credit
{
void pay(int cash,string creditNumber);
}public class CashDonate : Cash
{
public void pay(int cash)
{
Console.WriteLine(cash + " TL Bağışınız İçin Teşekkür Ederiz!");
}

}
public class CreditDonate : Credit
{
public void pay(int cash,string creditNumber)
{
Console.WriteLine(cash + " TL Bağışınız " + creditNumber + " Numaralı Hesabınızdan Çekilmiştir. Teşekkür Ederiz!");
}
}
public class CreditAdapter : Cash
{
private Credit _credit;
public CreditAdapter(Credit credit)
{
_credit = credit;
}
public void pay(int cash)
{
string yourCreditNumber = Console.ReadLine();
_credit.pay(cash, yourCreditNumber);
}
}

Var olan sisteme adepte ettiğimiz kredi kartı ile bağış yapalım: 💳

static void Main(string[] args)
{
Cash donate2 = new CreditAdapter(new CreditDonate());
donate2.pay(500);
}

Sonuç:

500 TL Bağışınız  500  Numaralı Hesabınızdan Çekilmiştir. Teşekkür Ederiz!

Bu şekilde adapter yardımıyla tek bir arayüzden işlemlerimizi gerçekliştiriyoruz. Dikkat edersiniz client direkt olarak adapter sınıfımıza erişmiyor. Bu sayede client’ın arayüzündeki kodu değiştirmeden sisteme yeni “adapter” ’ler ekleyebilir veya var olan “adepter” ’leri değiştirebiliriz.

3-)Behavioral Design Patterns

Bu tasarım kalıpları nesneler arası iletişim ile ilgilenir. Probleme göre nesneler arasındaki ilişkinin nasıl olması gerektiğini belirlememize yardımcı olur. Diğer bir deyişle nesneler arasında ortak iletişim kalıpları tanımlamamızı sağlar. Bu şekildeki tasarımlar iletişimin gerçekleştirilmesindeki esnekliği arttırır. Aşağıda “Behavioral Design Patterns” ‘ler verilmiştir:

  • Chain Of Responsibility
  • Command
  • Iterator
  • Mediator
  • Memento
  • Observer
  • State
  • Strategy
  • Template method
  • Visitor
  • Interpreter

Observer Pattern

Bu pattern gözlemlenen nesnenin değişikliğe uğraması durumunda diğer nesnelere bu değişiklikler hakkında bildirimde bulunmak için abonelik mekanizması oluşturmamıza izin veren bir design patterndir.

Öncelikle kavramlara odaklanalım:

  • Observable : Subscribe yani abone olunabilen nesneye denir. Diğer bir deyişle gözlemlenebilen nesnedir.
  • Observer : Subscribe olan yani abone olan nesneye denir. Diğer bir deyişle gözlemleyen nesnedir.

Nedir bunlar? Ve neden varlar?

Gerçek hayat üzerinden örnek vererek gitmek istiyorum Bu şekilde daha akılda kalıcı olacaktır diye ümit ediyorum. Örneğimi güncel bir platform olan Youtube Platformu üzerinden vereceğim. Youtube platformunda insanların kanallara abone olamadığını düşünün. Kanallar var fakat abone olamıyoruz. Ne garip değil mi? “X” isimli bir kanalı düşünelim. Bu kanal haftanın belirli günleri Youtube’da içerikler üretiyor olsun. Bu kanalı takip eden yüzlerce kişi var fakat kanala abone olma mekanizması yok. Bu yüzden herkes her gün “X” isimli kanal içerik üretti mi acaba diyerek bu kanalın sayfasına giriş yapıyor. Düşünsenize milyonlarca aboneye ulaşan “X” kanalını, bir günde milyonlarca insan sırf içerik üretildi mi diye ziyaret ediyor. Milyonlarca kişinin sürekli olarak bunu her gün veya belirli zamanlarda sürekli tekrarlamasının ne kadar maliyetli olduğunu anlatmama gerek yoktur diye düşünüyorum.

Gel zaman git zaman Youtube Yöneticileri abone olma sistemini Youtube Platformuna getiriyorlar. Bir zamanlar sürekli “X” isimli kanalı hemen hemen her gün kontrol eden milyonlarca kullanıcı artık “X” isimli kanala abone olma şansını elde ediyor. Bu sayede “X” isimli kanal içerik ürettiğinde milyonlarca kullanıcı gelen bildirim sayesinde üretilen içerikten haberdar oluyor.

Peki şimdi bana söyleyin burada “Observable” kimdir? Peki ya “Observer” ? Aslında anlaması çok da zor değil. Bizim burada bildirimleri almak için dinlediğimiz nesne gerçek hayattaki “X” kanalıdır. Yani “Observable” nesnemiz. “Observer” ‘ımız ise bizim kendi youtube hesabımızdır. Yani kullanıcılar. Bu şekilde sürekli olarak abone olduğumuz kanaldan gelecek bildirimleri dinleriz.

İşte “Observable Pattern” bize bu kolaylığı sağlar. “Observer” ‘lar “Observable” nesneyi dinler ve ve “Observable” nesnede değişiklik olduğunda bunu “Observer” ‘lara bildirir. Burada belirtmem gereken bir detay var. Normalde birden fazla “Observer” ‘ın subscribe olduğu nesneye “Observable” değilde “Subject” denilmektedir. Ancak birden fazla nesneye bildirimde bulunduğundan dolayı bazı kaynaklarda “Subject” , “Publisher” olarak’da karşısınıza çıkabilir.

Şimdi bu anlattıklarımızı uml diyagramı üzerinden inceliyelim. Ardından kodunu yazalım:

Diyagramı gözünüzde sakın ha büyütmeyin! Aslında olay gayet basit. Abone olma, abonelikten çıkarma ve diğer nesneleri bilgilendirme işlemini yapan metodlarımızı tek bir çatı altında “EventManager” sınıfında topladık. Ek olarak “EventManager ”sınıfmız List<IObservable> tipinde bir liste almaktadır. Dikkat ederseniz subscribe olma işlemini yapacak “personA” ve “personB” sınıflarımız “IObservable” arayüzünü uygulamaktadır. Bu arayüz ile birden fazla sınıf yani abone “EventManager” sınıfında değişiklik yapmadan bu sınıfa subscribe olabileceklerdir.

Kod:

public interface IObserver
{
void update(string content);
}
public class EventManager
{
private List<IObserver> pList = new List<IObserver>();
public List<IObserver> PersonList
{
get => pList;
}
public void subscribe(IObserver observer)
{
PersonList.Add(observer);
}
public void unsubscribe(IObserver observer)
{
PersonList.Remove(observer);
}
public void notify(string videoName)
{
foreach(IObserver observer in PersonList)
{
observer.update("X Youtube Kanalı '" +videoName+ "' İsimli Videoyu Yükledi");
}

}
}public class XYoutubeChannel
{
public EventManager manager;
public XYoutubeChannel()
{
manager = new EventManager();
}
public void createNewContent()
{
Console.WriteLine("Yeni Videonuzun İsmini Giriniz:");
string videoName = Console.ReadLine();
manager.notify(videoName);
}
}public class PersonA : IObserver
{
private string name;
private string surname;
public PersonA(string name,string surname)
{
this.name = name;
this.surname = surname;
}
public void update(string content)
{
Console.WriteLine("Merhaba " + name + " " + " " + surname + " " + content);
}
}
public class PersonB : IObserver
{
private string name;
private string surname;
public PersonB(string name, string surname)
{
this.name = name;
this.surname = surname;
}
public void update(string content)
{
Console.WriteLine("Merhaba "+name+" "+" "+surname+" "+content);
}
}

Abone Olma İşlemlerimizi Gerçekleştirelim:

static void Main(string[] args)
{
PersonA personA = new PersonA("Mehmed","Emre");
PersonB personB = new PersonB("Mert","Şahin"); XYoutubeChannel channel = new XYoutubeChannel(); //Abone Oluyoruz
channel.manager.subscribe(personA);
channel.manager.subscribe(personB);
channel.createNewContent();}

Sonuç:

Merhaba Mehmed  Emre X Youtube Kanalı 'Design Patterns' İsimli Videoyu YüklediMerhaba Mert  Şahin X Youtube Kanalı 'Design Patterns' İsimli Videoyu Yükledi

Genel olarak anlatacaklarım bu şekildeydi. “Design Pattern” ‘ler doğru kullanıldığın bizlere oldukça temiz ve takım çalışmasına uygun kod yazmamızı sağlıyor. Fakat her konuda olduğu gibi bu konuda da herkesin fikri aynı olamayabiliyor. “Design Pattern” ‘ler bilgisayar bilimleri alanında bazı kişiler tarafından eleştirilerde almıştır. Pattern’lerin uygulamayı çok fazla standartlaştırdığını ve gereğinden fazla kod tekrarına sebebiyet verdiğini söyleyeninden, bu pattern’leri kullananların yetersiz soyutlama becerisine sahip olduğunu veyahut bu patternlerin yetersiz programlama dillerinin bir getirisi olduğunu söyleyen ve bu konu hakkında makaleler yazan kişiler mevcut.

Bende geçenlerde yine bu platformda bazı “Design Pattern” ‘ler hakkında ağır eleştiriler içeren yazılara denk geldim. Hatta ve hatta bahsettikleri pattern’lere eleştiri oklarını öyle yöneltmişlerdi ki bu pattern’lerin kesinlikle kullanılmaması gerektiğini söyleyeceklerdi . Haklı oldukları yerler yok mu? Evet var. Fakat bu yanılmadıkları anlamına gelmiyor. Bir tane “SOLID” prensibini ihlal ediyor. Hayır bir değil iki tane “SOLID” prensibini ihlal ediyor gibi söylemler dolaşıyor. Büyük kurumsal ürünlerin kaç tanesinde “SOLID” ’e tamamen uyuluyor bunu da tartışmak gerekir. Bazen düşünüyorum da insanlar her zaman taraf olacak bir şey buluyor. Herkes kendi kullandığını veyahut kendi mantığına yatanı övüyor. Tabi ki de mantıklı eleştirilerde mevcut yine diyorum haklılık payları elbette ki var fakat bu patternlerin bizim problemlerimize bir şekilde çözüm ürettiğini unutmamamız gerekiyor.

Benim bu konu hakkında ki nacizane düşüncem şudur ki: Probleminize uygun ve ilerde sizin için faydalı olacak pattern’i sizin seçmeniz ve kullanmanız yönünde olacaktır. İnsanlarında önerilerini dinleyin. Tecrübelerinden faydalanın. Fakat öğrendiklerinizi ve aldığınız tavsiyeleri kendi düşünce süzgecinizden geçirmeyi unutmayın.

İçeriğimin sonuna gelmiş bulunmaktayım. Umarım her şey açıklayıcı olmuştur. İstemeden yazım hatalarım ve anlatım bozukluklarım olduysa şimdiden kusuruma bakmayın. 😅 Bazen ingilizce ve türkçe terimler iç içe geçebiliyor. Kendinize iyi bakın. Sağlıcakla kalın. Evde Kalın 😄

--

--