Dependency Inversion Principle DIP – Tasarım Prensibi

31 Ağu

Bu tasarım prensibi, uygulamalardaki alt ve üst sınıflar arasındaki bağımlılıklardan dolayı ortaya çıkmıştır. Alt sınıfları, ana işleri yapan sınıflar olarak düşünelim. Üst sınıflar da alt sınıflara bağlı olarak işlemler yapan sınıflar olarak düşünelim. Yani üst sınıflar bazı işlemleri gerçekleştirebilmek için alt sınıflara muhtaçtır. Doğal olarak üst sınıflar, alt sınıflardan oluşturulmuş nesneleri kullanarak süreci işleteceklerdir. Ancak üst sınıfların direk alt sınıf nesnelerine bağlı olması bir takım zorlukları beraberinde getirmektedir. Bu zorlukların başında sıkı bağ oluşumu gelmektedir. Sıkı bağlılık ise gelişimi ve test edilebilirliği olumsuz etkileyen bir durumdur.

Yazılım tasarımında sıkı bağlılığı ortadan kaldıran çözüm ise nesneleri direk kullanmak yerinde soyut nesneler yani arayüzler (interface) kullanmaktır. Yani üst sınıflar, nesnelerin kendisini tanımak yerine soyut nesneleri tanırlar.

Bir örnek üzerinden kötü ve iyi kod tasarımlarını inceleyelim.

Sıkı Bağlı Tasarım


public class DisplayServiceManager
{
     readonly AnalogDisplayService service = new AnalogDisplayService();

     public DisplayServiceManager(AnalogDisplayService service)
     {
         this.service = service;
     }

     public void StartService()
     {
         service.Start();
     }

     public void StopService()
     {
         service.Stop();
     }

}

public class AnalogDisplayService
{
     public void Start()
     {
          // Start analog display.
     }

     public void Stop()
     {
          // Stop analog display.
     }

}

Bu tasarımda örnek olarak görüntü yönetimini yapan DisplayServiceManager adında, üst sınıf olan bir servis yöneticisi oluşturulmuştur. Bu üst sınıf AnalogDisplayService adında bir alt sınıfı kullanmaktadır. Yukarıda belirttiğimiz gibi, üst sınıf alt sınıf nesnesini somut olarak kullanmıştır. Yani üst sınıf, servisleri tanımaktadır. Bu da alt sınıflara sıkı bağlılık oluşturmaktadır. Bu durumda DigitalDisplayService adında yeni bir ihtiyaca karşılık, yönetici üst sınıfın yapısı değişecek.

Yani şu anki durumumuz şu şekildedir:

Üst Sınıf + Alt Sınıf

Oysa istenen durum şu şekildedir:

Üst Sınıf + Soyut Nesne + Alt Sınıf

Bağımlılığa yeni bir boyut kazandıran tasarımı ise şu şeklide yapabiliriz.

Dependency Inversion Yöntemiyle Tasarımı


public class DisplayServiceManager
{
       private IDisplaySerivce service;

       public DisplayServiceManager(IDisplaySerivce service)
       {
            this.service = service;
       }

       public void StartService()
       {
            service.Start();
       }

       public void StopService()
       {
            service.Stop();
       }

}

public interface IDisplaySerivce
{
     void Start();
     void Stop();
}

public class AnalogDisplayService : IDisplaySerivce
{

     public void Start()
     {
        // Start analog display.
     }

     public void Stop()
     {
        // Stop analog display.
     }

}

Bu kod, Dependency Inversion prensibinin uygulandığı bir koddur. Sıkı bağlılığın IDisplayService arayüzü sayesinde ortadan kalktığını görmüş olduk. Yeni eklemeler için DisplayServiceManager sınıfında değişikliğe gerek kalmadı. Esnek bir yapıya sahip olduk.

Interface Segregation Principle ISP – Tasarım Prensibi

26 Ağu

Bu tekniği, arayüz ayırma prensibi olarak adlandırabiliriz. Uygulamalarımızda bağlı kalmaya çalıştığımız soyut nesnelerle çalışma ilkesi, bazı yanlış hamleler sonucu başımıza iş açabilir. Soyutluk kavramını da bazı kurallara göre işletmeliyiz. Bize soyut nesne sağlayan 3 tane metodu olan bir interface ürettiğimizi varsayalım. Bu interface den türeyen bir de sınıf oluşturalım. Benzer şekilde bir sınıf daha türetelim. İkinci sınıfımız interface nesnemizin tek bir metodunu kullanıyor ve diğer iki metodu işe yaramıyorsa, doğru yolda değiliz demektir.

Cümlelerden ziyade bir örnek üzerinden anlatmak daha isabetli olacaktır.

Örneğimizde aşağıdaki gibi IUser tipinden iki seviyeli LowLevelUser ve HighLevelUser kullanıcı tipleri oluşturuyoruz. LowLevelUser sadece programları update edebilir. HighLevelUser ise hesap oluşturup hesap silebilir. Ancak LowevelUser tipinin CreateAccount metodu tetiklendiğinde NotImplementedException hatası ile karşılaşırız.


public interface IUser
{
      void UpdateProgram(string programme);
      void CreateAccount(string accountName);
      void DeletaAccount(string accountName);
}

public class LowLevelUser : IUser
{
      public void UpdateProgram(string programme)
      {
         // Start update service
      }

      public void CreateAccount(string accountName)
      {
         throw new NotImplementedException();
      }

      public void DeletaAccount(string accountName)
      {
          throw new NotImplementedException();
      }

}

public class HighLevelUser : IUser
{
      public void UpdateProgram(string programme)
      {
          // Start update service
      }

      public void CreateAccount(string accountName)
      {
          // Start create service
      }

      public void DeletaAccount(string accountName)
      {
          // Start delete service
      }
}

IUser arayüzünü parçalayarak işimize yarar hale getirmeliyiz. Yani alt arayüzler oluşturmalıyız.

Bu ayrımı şu şeklide yapabiliriz:


public interface IUpdater
{
      void UpdateProgram(string programme);
}

public interface IAccountManager
{
      void CreateAccount(string accountName);
      void DeletaAccount(string accountName);
}

public class LowLevelUser : IUpdater
{
      public void UpdateProgram(string programme)
      {
          // Start update service
      }
}

public class HighLevelUser : IUpdater, IAccountManager
{
      public void UpdateProgram(string programme)
      {
         // Start update service
      }

      public void CreateAccount(string accountName)
      {
         // Start create service
      }

      public void DeletaAccount(string accountName)
      {
         // Start delete service
      }
}

LowevelUser seviyeli kullanıcı tipinin sadece update işlemlerine yetkili olduğu için UpdateProgram metodunu içeren IUpdater arayüzünden türetilmiştir. Aynı şekilde HighLevelUser ise hesap yönetimlerinden sorumlu olduğu için CreateAccount ve DeleteAccount metodlarını içeren IAccountManager arayüzünden türetilmiştir.

Bu sayede arayüz ayırım tasarım prensibinin amacına ulaşabilmiş oluyoruz.

Liskov’s Substitution Principle LSP – Tasarım Prensibi

23 Ağu

Bu ilkenin benimsediği ana fikir şu şekildedir: “Alt sınıflardan oluşan nesneler üst sınıfların nesneleriyle yer değiştirdiklerinde aynı davranışı sergilemelidir.” Yani kullanıcılar (client) tarafında alt sınıf ve üst sınıfın kullanımı bir fark göstermemelidir.  Cümlelerle ifade etmek anlaşılır olsa da, kavramı örneklerle güçlendirmekte fayda var.


public abstract class Employee
{
     public abstract string GetDepartment();
}

public class ITEmployee : Employee
{
     public override string GetDepartment()
     {
          return "Information Technology Department";
     }
}

public class HumanResource : Employee
{
      public override string GetDepartment()
      {
         return "Human Resources Department";
      }
}

Yukarıdaki örnekte Employee adında bir üst ve bu sınıftan türetilmiş ITEmployee ve HumanResource alt sınıfları gösterilmiştir. Bu yapıya göre, alt sınıflar üst sınıf gibi kullanılabilir. Nasıl olduğunu şu şeklide client tarafında gösterebilriz.

static void Main(string[] args)
{
     Employee employee1 = new HumanResource();
     Employee employee2= new ITEmployee();

     Console.WriteLine(employee1.GetDepartment());
     Console.WriteLine(employee2.GetDepartment());
}

Employee tipine ait nesne(employee1), HumanResource veya ITEmployee gibi kullanılabilmiştir. Yani alt sınıflar, üst sınıfın işini üstlenebilmektedir. Yani yer değiştirme işlemi başarıyla gerçekleştirilebilmektedir.

LSP temelde Open Closed prensibinin özel bir türü veya uzantısı gibidir. Çünkü OCP den olduğu gibi LSP de de genişlemeye açık bir yapı sözkonusudur.

Open Closed Principle OCP – Tasarım Prensibi

20 Ağu

İyi kodun üretilmesi, bazı kurallara dikkat ederek mümkün olabilmektedir. Bu kurallarda biri de kodun sürekli gelişebileceğinin göz önünde bulundurulmasıdır. Sonuçta biz bir inşaat mühendisi değil, yazılımcıyız. İnşaat mühendisinin yaptığı yapıyı değiştirmesi neredeyse imkansızdır. Ancak yazılım mühendisinin üretimini her zaman geliştirme ve değiştirme imkanı vardır. Ancak bazen öyle kodlar yazarız ki kodumuzu, içerisine beton dökülmüş gibi bir hale sokar, kodu tekrar düzenleme ve yenileme işlemini imkansızlaştırırız.

Kodlama sırasında birçok değişiklik, uygulamalara yeni özellikler eklerken ortaya çıkmaktadır. Kodda yapılandırma işleminin, kodumuzu en az seviyede etkilemesini amaçlamalıyız. Kodun en az seviyede etkilenmesini sağlamak için kodu üretirken bazı durumları göze alarak hareket etmemiz gerekir. Örneğin mevcut kod zaten bir test işleminden geçerek üretilmiş olabilir. Yeni eklemelerle mevcut test durumlarını yeşilden kırmızıya düşürmemek gerekir. Özetle kodumuz geliştirilmeye açık (Open), değiştirilmeye kapalı(Closed) olmalıdır.

Bu tasarım ilkesini bir örnek üzerinden anlatmaya çalışalım. Öncelikle, open closed tasarım ilkesinin kullanılması için bir sebebimizin olması gerekir. Yani durduk yere bu prensip ortaya çıkmış değil. Hatalı bir çözümün sonucunda ortaya çıkmış olmalı.


public class FileGenerator
{
    private readonly FileBase baseFile;
    public FileGenerator(FileBase bBase)
    {
          baseFile = bBase;
    }

    public string Create()
    {
      if (baseFile is PDF)
      {
         var item = baseFile as PDF;
         return item.Create();
      }

      if (baseFile is WORD)
      {
         var item = baseFile as WORD;
         return item.Create();
      }

      return null;
    }
}

public class FileBase
{
}

public class PDF: FileBase
{
   public string Create()
   {
       return "PDF File";
   }
}

public class WORD : FileBase
{
     public string Create()
     {
        return "WORD File";
     }
}

Bu örneğimizde FileBase tipindeki bir temel tipten türetilmiş WORD ve PDF tipleri oluşturulmuştur. Bir tane de dosya oluşturma işlemini gerçekleştirecek olan FileGenerator tipi oluşturulmuştur. FileGenerator sınıfının Create metodu, base tipine göre dosya üretimi yapmaktadır. Buraya kadar hesaplanan  düzenek çalışır vaziyettedir. Ancak bu düzenekte bazı sorunlar vardır. Bunlar:

  • FileGenerator tipi, dosya tipleriyle (WORD, PDF) birebir ilgilenmekte. Yani nesnelere direk eriştiğinden sıkı bağlılık sözkonusu.
  • Yeni bir dosya tipi eklendiğinde FileGenerator sınıfının Create metodunda değişiklik yapılmak zorunda. Oysa değişikliklere kapalı bir sistem olmalı. Ne kadar dosya tipi oluşturulursa oluşturulsun, FileGenerator sııfı etkilenmemeli. Yani gelişime açık sistem olmalıdır.
  • FileGenerator tipine ait create metodunun testleri her yeni ekleme

[Test]
public void FileGenerator_Generates_PDF()
{
    var file = new FileGenerator(new PDF());
    var created = file.Create();
    Assert.AreEqual("PDF file",created);
}

Yani yukarıdaki tes metodu, WORD için, PDF için çalışırken, FileBase tipinden türetilmiş EXCEL şeklinde yeni bir tip için çalışmayacaktır. Create metodunda değişiklik isteyecektir.

Bu gibi dezavantajları göz önünde bulundurarak yeni bir yapı tasarlayalım.


public class FileGenerator
{
    private readonly IFileBase fileBase;

    public FileGenerator(IFileBase fileBase)
    {
         this.fileBase = fileBase;
    }

    public string Create()
    {
         return fileBase.Create();
    }
}

public interface IFileBase
{
     string Create();
}

public class PDF : IFileBase
{
    public string Create()
    {
        return "PDF file";
    }
}

public class WORD : IFileBase
{
    public string Create()
    {
        return "WORD file";
    }
}

Bu tasarımda, FileGenerator ile dosya tipleri arasında IFileBase interface ile bir esneklik sağlanmıştır. Bu sayede FileGeerator tipinin sadece temel tip olan IFileBase tipinden haberi vardır. WORD, PDF gibi tipleri tanımaz. Bu yüzden yeni eklemeler(örn. EXCEL) FileGenerator sınıfında hiçbir etki yapmayacaktır.Yani değişikliğe kapalı (Cosed) şartını sağlamış olduk. İstediğimiz kadar yeni dosya tipi üretebiliriz. Böylece genişlemeye  de açık (Open) olma şartını sağlamış olduk.

Artık birim(metod) testler de değişikliklere dayanıklı hale gelmiş oldu. Yeni bir tip üretip testten geçirebiliriz.

Sürekli değişikliklere rahat cevap verebiliriz ve müşterilerden gelen yeni dosya tiplerine karşı hazırlıklıyız.

Tekrar görüşmek ümidiyle…

Single Responsibility Principle SRP – Tasarım Prensibi

16 Ağu

Bu tasarım prensibi, her işten tek sınıfın sorumlu olmasını benimsemektedir. Yani bir sınıfta değişiklik yapmak için iki nedeniniz varsa, bu sınıfı ikiye bölmeniz gerekir. Böylece  her bir sınıf, ayrı bir sorumluluğu üstlenmiş olur. Eğer bir sınıfa birden fazla sorumluluk yüklersek, sınıftaki değişiklikler sırasında işleyişi bozabiliriz.

Örnek uygulama ile devam edelim.


public class EmailService: IEmailService
{
      public void sendEmail(string email)
      {
          if(isUserAuthorized())
          {
              // email gönder
          }
      }

      private bool isUserAuthorized()
      {
           // veritabanına git
           // query user
           // user varsa ve yetkiliyse
           return true;
      }

}

Yukarıdaki EmailService sınıfı, hem Email gönderme işlemini yapmaktan hem de kullanıcının veritabanında olup olmadığını kontrol etmekle sorumludur. Yani kullanıcı kontrolü ve Email gönderme işlemleri farklı işlemler. Bu durumda sınıfta değişiklik yapmak için iki nedenimiz var. Bunu şu şeklide Single Responsibility Prensibine uygun hale dönüştürebiliriz:


public class EmailService: IEmailService
{
      public void sendEmail(string email)
      {
         if(MembershipService.IsUserAuthorized("bayram"))
         {
             // email gönder
         }
      }
}

public class MembershipService
{
      public static bool IsUserAuthorized(string user)
      {
          // veritabanına git
          // query user
          // user varsa ve yetkiliyse
          return true;
      }
}

Bu durumda her sınıf kendi sorumluluğunu yerine getirmiş oldu.