Welcome to My Blog 👋

Java, Spring Framework, Microservices, Docker, Kubernetes, AWS and Others 🚀
Follow Me
Showing posts with label doğru yazılım tasarımı. Show all posts
Showing posts with label doğru yazılım tasarımı. Show all posts

Anti Pattern'lar aynı Design Pattern'lar gibi zamanla kalıplaşmış yapılardır. Design Pattern'larından farkı bir problemin nasıl olması gerektiğini değil nasıl olmaması gerektiğini anlatmasıdır. Bu yönüyle Design Pattern'larının zıttıdır diyebiliriz. Bazı Anti Pattern örnekleri;

  • Magic Pushbutton : Frontend elemanların kodları arasına iş mantığını sağlayan kodların yazılması ile oluşur.
  • Functional Decompozition : Birbirinden alakasız statik fonksiyonları tek bir sınıfa toplanması ile oluşan bir anti pattern'dır.
  • Database as IPC : Farklı uygulamalar arasındaki iletişimin bir veritabanı üzerinden yapılması ile oluşur.
  • Swiss Army Knife : Kullanılmayan, gereksiz kod parçalarının bulunduğu yazılımlarda görünür.
  • Busy Waiting : Zaman alan işlemleri aynı thread üzerinde yaparak uygulamanın yanıt vermemesi durumunu oluşturan kodlara denir.
  • Soft Coding : İş mantığını konfigürasyon dosyası gibi dosyalarda kodlanmasına denir.
  • Lasagna Coding : Gereğinden fazla katmandan oluşan kodlara denir.
  • Spagetti Coding : Soyutlama gibi tasarım prensipleri uygulamadan yazılmış karmaşık kodlara denir.
  • God Object : Birden fazla amacı olan çok fazla kod bulunan sınıflara denir.
  • Error Hiding : Uygulamada oluşan hataları handle eden ve bu hataları loglamayarak üstünü kapatmaya çalışan yazılımlarda görünür.
  • Copy Paste Programing : Hız kazandırdığı düşüncesiyle yapılan ancak daha sonradan çok fazla soruna yol açabilecek bir tekniktir.

Design Pattern yani dizayn paternleri programlamada sürekli karşılaşılan problemlere en iyi çözümü sağlayan kalıplaşmış yöntemlerdir denilebilir. Bu yüzden programlama dilinden bağımsız yapılardır. Ben yazılarımda dizayn paternlarını anlatırken Java dilini kullanıyor olacağım ancak bu yapılar tüm nesneye yönelik programlarda kullanılabilir.
Dizayn paternlarının amacı programcının sürekli karşılaşılan problemler için çözüm aramamasını sağlamak ve daha önceden yapılan ve yanlış olduğu görülen yöntemler denememesini sağlamaktır. Bu yapılar zamanla kalıplaşmıştır ve en doğru çözüm olduğu konusunda hemfikir olunmuştur.
Dizayn paternları kendi içinde mantıksal olarak ayrılır. Aşağıdan bu paternlar ile ilgili yazdığım yazılara ulaşabilirsiniz.

Creational Pattern'lar (Nesne Yaratılışına Ait Patern'lar)


Structural Pattern'lar (Yapısal Patern'lar)


Behavioral Pattern'lar (Davranışsal Patern'lar)



Yazılım sistemlerinde değişiklik kaçınılmaz bir durumdur. Yazılan kod her zaman değişmek zorunda kalır. Bu yüzden yazdığımız kodu kolay değişebilir şekilde yazmalıyız. Aksi takdirde yazdığımız kod çok kısa bir zaman sonra çöp olacaktır. Bu problemi çözümü nesne yönelimli programlamada polimorfizm'dir. Polimorfizm ile sistemimizde bir değişiklik yapmak zorunda kaldığımızda mevcut kodları değiştirmeden sadece yeni kodlar ekleyerek problemlerimizi çözebiliriz. Bu işlemin amacı daha önceden yazılmış kodun bağımlılıklarından kurtulmaktır. Çünkü bir kodu değiştirmek her ne kadar kolay gözüküyor olursa olsun o kodun sistemimizde bağımlı olduğu yerleri düşündüğümüzde yani etkilediği diğer kod parçalarını düşündüğümüzde işin içinden çıkılamaz bir durum oluşur. Bu yüzden polimorfizm kullanarak sistemlerimizi tasarlamalıyız.

Polimorfizm kısaca, yazılımda uyumluluğu yani geçmişte yazılan kodların, gelecekte yazılacak olan kodları desteklemesini sağlayan bir nesne yönelimli programlama tekniğidir.

Polimorfizm iki şekilde gerçekleştirilebilir.
  • Soyut sınıf ve saf sanal fonksiyonlar kullanılarak.
  • Interface yani arayüzler kullanılarak.

Aşağıdaki iki örnekte de sisteme yeni bir şekil eklendiği zaman Ciz sınıfında veya sistemin herhangi bir yerinde bir kod değişikliği yapmamıza gerek olmaz.

Soyut Sınıf Örneği


public class Ciz{

     public void sekilCiz(Sekil sekil){
         sekil.sekilCiz();
     }
}

public class Sekil {
    
    public void sekilCiz(){
        
    }
}

public class Kare extens Sekil {
    
    public void sekilCiz(){
        
    }
}

public class Daire extens Sekil {
    
    public void sekilCiz() {
        
    }
}

Interface Örneği

public class Ciz{

     public void sekilCiz(Sekil sekil){
         sekil.sekilCiz();
     }
}

public interface Sekil {
    
    public void sekilCiz();
}

public class Kare implements Sekil {
    
    public void sekilCiz(){
        
    }
}

public class Daire implements Sekil {
    
    public void sekilCiz() {
        
    }
}


Bir yazılım birden fazla şekilde yazılabilir ancak her çalışan kod gerçekten bir yazılım sayılmaz. Örneğin inşaat mühendisleri bir binayı tasarlarken sadece binayı yapmayı düşünmezler. Tüm olası durumlarda o binanın ayakta durması için gerekli olan şeyleri düşünüp ona göre binayı inşa etmek zorundadırlar. Yazılım mühendislerininde böyle bir zorunluluğu vardır çünkü her ne kadar kod çalışıyor olsa bile iyi tasarlanmamış bir kodun ömrü çok kısa olacaktır. Bu yüzden yazılımı yazmadan önce o yazımı iyi tasarlamak gerekir. Aksi halde işin içinden çıkamayacak problemlerle karşılaşılır ve kod bir çöpe dönüşür. Bazı yazılım prensipleri;

  • Yazılımda olacak paydaşları doğru bir şekilde çıkararak bunları yazılıma aktarmamız gerekir. Gerçek dünya nesneleri olarak düşünerek bunların yazılımdaki karşılıkları olan sınıfları çıkarmalıyız. Bunu yaparken her bir paydayı ayırmalıyız. Örneğin müşteri, satıcı olan ve hatta bu tiplerin alt türlerinin de bulunduğu bir yazılımda tüm bu paydaşlar için tek bir sınıf yaparsak daha sonraki herhangi bir değişiklikte işin içinden çıkılamaz bir karmaşıklık oluşur. Bunun için buraki tüm paydaşları amacına göre tek tek ayırarak sistemi tasarlamamız gerekir.
  • Bir sınıfı tasarladığımızda o sınıfın üyeleri yani özellik ve fonksiyonları mantıksal açıdan birbirlerine yakın olmalı ve benzer bir amaç için var olmalıdırlar. Yani aynı sınıfta birbirinden amaç olarak uzak olan iki üye olmamalıdır. Böyle bir durum var ise muhtemelen yanlış bir tasarım yapıyoruz demektir.
  • Bir sınıfı tasarladığımızda o sınıfın tek bir amacı olmalıdır. Birden fazla amaca hizmet eden sınıflar kötü tasarımı gösterirler. Yazılımda bir değişiklik olacağı zaman muhtemelen bir amaca yönelik olur. Eğer sınıflarımızı doğru bir şekilde tanımlarsak herhangi bir ihtiyaçta çok az kod değişimi yaparak belki de hiç kod değiştirmeden sadece biraz kod ekleyerek problemi çözebiliriz.
  • Tasarladığımız sınıflar genellikle birbirleri ile etkileşim içerisinde olurlar. Bu kaçınılmaz bir durumdur ancak bu bağımlılıklar kodda bir değişiklik yapmak istediğimizde karşılaştığımız en büyük problemdir. Bu yüzden sınıfların arasında mümkün olduğunca az bağımlılık yaratmalıyız ve yarattığımız bağımlılıkların seviyesini mümkün olduğunca düşürmeliyiz. Bu seviyeyi düşürmenin en güzel yolu da polimorfizm'dir.
  • Tasarladığımız sınıflarda yeniden kullanılabilirliği düşünmeliyiz. Örneğin bir sınıfa eklediğimiz özellikler başka sınıflarda da kullanılabilecek genel özellikler ise bu özellikleri tekrar tekrar yazmak yerine yeniden kullanılabilirliği sağlayarak genelleştirmeli ve tüm sınıflarımızda kullanmalıyız. Böylelikle genel yapıda bir değişiklik yapmak istediğimizde tüm sınıflarda değişiklik yapmak yerine tek bir noktada değişiklik yapabiliriz.
  • Tasarladığımız sistemlerde değişimin olmaması imkansızdır. Yazılım sistemlerinde değişim kaçınılmaz bir durumdur. Bu yüzden sistemimizi buna göre tasarlamak zorunda olmalıyız. Sistemimizi buna göre tasarlarken dikkat etmemiz gereken nokta bir değişiklikte kod değişikliği yapmak yerine sisteme yeni kodlar eklemek olmalıdır. Çünkü sınıflar arasındaki bağımlılıktan dolayı yapılacak bir kod değişiminin etkisi çok büyük olacaktır. Bu yüzden kod değişimi yerine kod eklemesi yapmalıyız ve sistemimizi buna göre tasarlamalıyız. Bunu yapmanın en kolay yolu polimorfizm'dir.

Örnek 

Aşağıdaki örnekte kötü bir tasarım örneğini görüyoruz. Bu sisteme yeni bir şekil eklediğimizde Ciz sınıfı içerisindeki metodu değiştirerek if bloğu içerisine bir koşul daha yazmamız gerekecektir. (EnumDegeri ile mantıksal olarak bir enum varmış gibi düşünülmüştür. Burada EnumDeğeri yerine mantıksal bir parametre veya buna benzer bir yapı olabilir önemli olan bu tür kullanımın yanlışlığının gösterilmesi olduğu için enum koda eklenmemiştir.)

public class Ciz{

     public void sekilCiz(EnumDegeri){
         if(EnumDegeri == Kare) {
             Kare kare = new Kare();
             kare.kareCiz();
         } else if(EnumDegeri == Daire) {
             Daire daire = new Daire();
             daire.daireCiz();
         } 
     }
}

public class Sekil {
    
    public void sekilCiz(){
        
    }
}

public class Kare extens Sekil {
    
    public void kareCiz(){
        
    }
}

public class Daire extens Sekil {
    
    public void daireCiz() {
        
    }
}

Bu problemi çözerek daha iyi bir tasarım yapmak için sistemimizi yeni bir şekil eklendiğinde hiç bir kod değişikliği yapılması gerektirmeyecek şekilde tasarlamamız gerekmektedir. Bu tasarım polimorfizm ile gerçekleştirilir. Bu şekilde sistemimize yeni bir şekil eklendiği zaman Ciz sınıfı içerisinde hiç bir değişiklik yapmaya gerek kalmadan sistemimiz çalışacaktır.


public class Ciz{

     public void sekilCiz(Sekil sekil){
         sekil.sekilCiz();
     }
}

public class Sekil {
    
    public void sekilCiz(){
        
    }
}

public class Kare extens Sekil {
    
    public void sekilCiz(){
        
    }
}

public class Daire extens Sekil {
    
    public void sekilCiz() {
        
    }
}