ASP.NET Core geliştiriciler için yol haritası

5 Oca

2019 yılında, bir grup geliştirici, GitHub‘da ASP.NET Core geliştiriciler için bir yol haritası ortaya koymuşlar. Kaynağa buradan ulaşabilirsiniz.

Bu yazıda, her geliştirici için faydalı olabileceğini düşündüğüm bu yol haritasını sizlerle paylaşmak istedim.

Yol Haritası Boyunca Bilinmesi Gerekenler

  1. Öncelikle bilinmesi gerekenler.
    • C#
    • Entity Framework
    • ASP.NET Core
    • SQL Fundamentals
  2. Bilinmesi gereken genel geliştirme becerileri.
    • GIT sistemi öğrenilmeli, GitHub üzerinde yeni bir repository oluşturulabilmelidir, kod diğer geliştiricilerle paylaşılmalıdır.
    • HTTP(S) protokolü bilinmeli, http request metodları (GET, POST, PUT, PATCH, DELETE, OPTIONS) bilinmelidir.
    • Google kullanmaktan ve araştırmaktan korkmayın.
    • Dotnet CLI öğrenmelisiniz.
    • Veri yapıları ve algoritmalar hakkında bir kaç kitap okumalısınız.
  3. Dependency Injection (DI – Bağımlılıkların dışarıdan alınması)
  4. Veritabanları
  5. Cache Mekanizmaları
  6. Log Mekanizmaları
  7. Web site geliştirme şablonları (Template Engines)
  8. Gerçek zamanlı iletişim araçları (Realtime Communication)
  9. Nesne eşleştirme araçları (Object Mapping)
  10. API istemcileri (Clients)
  11. Bilinmesi faydalı olabilecek bilgiler
  12. Testler
  13. Görev zamanlayıcılar (Task scheduling)
  14. Mikro servisler (MicroServices)
  15. SOLID prensipleri
  16. Tasarım Kalıpları (Design-Patterns)

Yol Haritası Görseli

Kullanıcı Dostu Yazılım Nasıl Anlaşılır?

13 Haz

Yazılımda kullanıcı dostu (user friendly) kavramı arayüzle ilgili bir kavramdır. İçgüdüsel olarak, yazılımımız kullanıcının hoşuna giden rahat bir kullanıma sahipse kullanıcı dostudur denir. Ancak bu durumun ispatlanabilir ve bilimsel olarak ifade edilebilir olması gerekmektedir. Aksi halde yazılımınızın kullanıcı dostu olup olmadığı havada duran bir ifade olarak kalır.

Bir yazılımın kullanıcı dostu olup olmadığı, arayüz değerlendirmesi kabul testleri ile meydana çıkan bir kavramdır. Yazılımın analiz aşamasında, yazılım performansının değerlendirilebilmesi amacıyla objektif ve ölçülebilir hedeflerin belirlenmesi gereklidir. Değerlendirme yapılırken niyeti açık bir şekilde ortaya koyan kabul kriterleri oluşturulur ve bunlar genellikle aşağıdaki maddeleri içerir:

  • Öğrenme süresi
  • Hata oranı
  • Görevin tamamlanma hızı
  • Akılda kalıcılık

1- Öğrenme Süresi
Bir kullanıcının, sistemi öğrenme süresi yazılımın kullanılabilirliği açısından önemli bir ölçümdür. Karmaşık bir arayüze sahip yazılımın öğrenme süresi ve dolayısıyla müşteriye olan maliyeti yüksektir. Bu istenmeyen bir durumdur.

2- Hata oranı
Sistemin kullanımı sırasında meydana gelen toplam hata veya belli bir süre içerisinde oluşan hata sayısıdır. Çok fazla hata uyarısı vermek veya istenmeyen hataların(exception) oluşması , kullanıcı üzerinde hoş bir izlenim bırakmaz.

3- Görevin Tamamlanma Hızı
Görevin tamamlanma hızı yazılımın bir işi bitirme hızı yani yazılımın performans göstergesi değildir. Kullanıcının arayüzü kullanabilemsi ile ilgili gözlenen hızdır. Örneğin bir yeni bir kaydın eklenebilmesi kullanıcının ne kadar zamanını aldığını gösteren hızdır.

4- Akılda Kalıcılık
Zaman içinde arayüzün akılda kalıcılığı ile ilgilidir. Arayüzün kısa bir süre içinde unutulmaması gerekir.

Örnek bir kabul testi nasıl olmalıdır?

  • Bir saatlik eğitim sonunda, banka görevlisi yeni bir müşteri kaydını hatasız olarak yapabilmelidir.
  • Beş saatlik eğitim sonunda, banka görevlisi müşteriler arasında para transferini hatasız bir şekilde gerçekleştirebilmelidir.

Sistemin tasarımı ve kodlanması tamamlandıktan sonra, analiz aşamasında belirlenmiş olan hedeflere bakılarak kullanıcı kabul testleri gerçekleştirilir. Arayüzün testi sırasında, ölçülebilir kriterlerin incelenmesi büyük önem taşır. Böylece bilimsel temeli olmayan “kullanıcı dostu olup olmama” tartışmalarından kaçınılmış olur. Yani kullanıcı dostu (user friendly) diye tabir edilen ama tarif edilemeyen kriterin anlaşılabilir bir şekilde ortaya koyulmuş ve bu kriteri ölçebilen bir ölçek oluşturulmuş olur. Kabul testinin amacı, gereksinim dökümanlarında belirtilmiş olan şartların yerine getirilip getirilmediğini kontrol etmektir. Sistem bu testleri geçtikten sonra, seri üretime geçilir ve aktif olarak kullanımı sırasında da incelenmesi devam edecektir.

Application Repository Ninject Modülü

29 May

Bu yazımızda, veri merkezli(data centric) uygulamalarda alışılagelmiş bir yöntem olan Repository kullanımının, birden fazla veri kaynağının bulunduğu durumlarda nasıl yapılması gerektiği konusunu incelemeye çalışacağız.

Repository mantığında öncelikle tüm alt sınıfları hiyerarşik bir düzene sokmak adına tanımlanan bir interface vardır. Örnek olarak basit bir interface tanımlamasını şu şekilde ifade ediyor olalım.


public interface IRepository<T>
{
    void Add(T entity);
    IList<T> GetAll();
}

Bu interface altında veritabanına ekleme ve veritabanından veri getirme gibi iki temel işlem vardır. Eğer bir SQL veri tabanına veri yazmayı amaçlıyorsak bu işlemi gerçekleştiren somut sınıfın oluşturulması gerekmektedir.


 public class SqlRepository<T> : IRepository<T>
 {
   public void Add(T entity)
   {
       // Veritabanına ekle..
   }

   public IList<T> GetAll()
   {
       // Veritananından oku..
   }
 }

Buraya kadar işler normal akışında seyrediyor. Ancak SQL veri tabanına yazılamadığı durumlarda ne olacağı bir soru işareti. Ya da başka bir sebepten dolayı ilişkisel veri tabanına yazmak yerine kayıtları XML bir dosyaya yazmak gerektiğinde XML dosya ile çalışabilen somut bir repository oluşturulması gerekmektedir. Bu durumda yeni bir sınıfa ihtiyaç duyulmaktadır.


 public class XmlRepository<T> : IRepository<T>
 {
    public void Add(T entity)
    {
       // XML dosyaya ekle...
    }

    public IList<T> GetAll()
    {
       // XML dosyadan oku...
    }
 }

Ancak bu iki farklı Repository tipinin, çalışma zamanında seçilebilir ve değiştirilebilir olması gerekmektedir. Aksi taktirde kodun değiştirilebilir olması istenmeyen bir durumdur. Bu değişime ayak uydurabilmek adına, değişikliği otomatik olarak gerçekleştirebilecek bir sınıfa ihtiyaç duyulmaktadır. Bu sınıf, çalışma zamanında dependency injection mekanizması ile yüklenebilen bir modül halinde tasarlanabilir.


 public class ApplicationRepositorySelectionModule : NinjectModule
 {
    public override void Load()
    {
        switch (ApplicationConfiguration.StorageType)
        {
           case PersistenceStorageType.SqlRepository:
              Bind(typeof(IRepository<>)).To(typeof(SqlRepository<>));
              break;
          case PersistenceStorageType.XmlRepository:
              Bind(typeof(IRepository<>)).To(typeof(SqlRepository<>));
              break;
         default:
              throw new ApplicationException("Not supported storage type!");

       }
    }
 }

NinjectModule sınıfı, Ninject dependenjy injector aracına ait bir sınıftır. Yaptığı işlem ise  Interface türlerine karşılık hangi somut sınıfın oluşturulacağını belirlemektir. Zamanı geldiğinde ilgili nesneyi oluşturup programa sağlamak yine Ninject tarafından gerçekleştirilmektedir.

Modül içerisinde kullanılan PersistenceStorageType ve ApplicationConfiguration sınıfları ise şu şekildedir.


 public class ApplicationConfiguration
 {
     public static PersistenceStorageType StorageType { get; set; }
 }

 public enum PersistenceStorageType
 {
     SqlRepository = 0,
     XmlRepository = 1
 }

Bu şekilde birden fazla repository sınıfı ile konfigurasyonu sağlababilir şekilde çalışmak mümkün olmaktadır. Yazılımda çevikliği yani yazılımın değişime ayak uydurabilme yeteneğini sağlayabilmek açısından uygulanabilecek olan küçük bir detaydan bahsetmiş olduk.

Bir sonraki yazıda görüşmek dileğiyle.

Bir İki Cümleyle Design Patterns

17 Eyl

Factory Pattern
Nesne oluşturma işleminin bir iş kuralı dahilinde olduğu durumlarda kullanıcıyı kuralın dışında tutarak nesne oluşturmak için kullanılır. Kurallara uygun bir şekilde soyut tipleri implemente eden  somut nesneler oluşturularak kullanıcılara verilir.

Abstract Factory Design Pattern
Factory nesneleri oluşturmak için kullanılan bir desendir. Yani Abstract Factory deseni ile soyut bir factory nesnesi oluşturulur, bu factory nesnesinden de belirlenen kural dahilinde bir nesne oluşturulur. Küme alt küme mantığına benzer bir yapı söz konusudur.

Adapter Design Pattern
Birbiriyle uyumsuz iki farklı interface arasında anlaşma sağlayan bir köprü vazifesi görür. Bunu gerçek hayatta cep telefonu ile elektrik prizi arasında görev yapan şarj aletine benzetebiliriz.

Template Pattern
Bir algoritma için soyut tipte bir iskelet oluşturarak bu iskeletten somut süreç adımları oluşturmamızı sağlar. Algoritmanın adımlarını somut sınıflarda tanımlarız. İskelet görevi gören tip bir şablon niteliğindedir.

Singleton Design Pattern
Bir sınıfa ait sadece tek bir nesne oluşturmayı ve gerektiğinde ona ulaşabilmeyi garanti eder. Yani nesne oluşturma üzerine oluşturulmuş bir desendir.

Decorator Pattern
Nesnelere kompozisyon yoluyla yeni davranışlar eklememizi sağlar. Bu işlemi aynı temel sınıftan türeterek veya ortak bir interface uygulayıp nesneye enjekte ederek yapabilmek mümkündür.

State Pattern
Bir nesnenin iç durumunda meydana gelen değişikliklerin nesnenin davranışını değiştirmesine izin verir. İç durumdan kasıt interface tipleridir.

Strategy Pattern
Çalışma zamanında dinamik olarak algoritma değişikliğine olanak sağlayan bir desendir. Algoritmalar somut nesnelerde saklanır. Client kod ise algoritma sınıfının türetildiği abstract veya interface türünü tanır.

  • Not: State ve Strategy pattern diyagram olarak birbirine benzerdir. Ancak çalışma prensipleri açısından birbirlerinden farklıdırlar. State pattern için nesnenin NE olduğu önemlidir. Strategy için nesnenin NASIL çalıştığı önemlidir.

Specification Pattern
İş nesneleri içerisinde gömülü olan seçim kriterlerini başka nesneler ile paylaşamazsınız veya tekrar tekrar kullanamazsınız. Specification pettern, bu problemi ortadan kaldırır.

Composite Pattern
Nesnelere ağaç yapısında ya da hiyerarşik topluluklar halinde gruplanabilme yeteneği sunar. Örneğin bir kategorinin alt kategorisi olabileceği gibi onun da alt kategorisi olabilir.

Proxy Design Pattern
Bir sınıfın işlevselliği başka bir sınıf ile temsil edilir. Cache mekanizmaları oluşturulurken kaynak kullanımını azaltmak için kullanılabilir. Örneğin veri tabanından veri getiren bir nesne, aynı verilere ihtiyaç duyulduğunda tekrar veri tabanına gitmeye gerek duyulmadan kullanılabilir.

Builder Design Pattern
Kuramsal bir iş akışını işletmek için gerekli olan kompleks bir nesnenin basit nesneler ile adım adım oluşturulmasına olanak sağlar.

Bir Metodun Single Responsibility Principle Bakımından İncelenmesi

4 Oca

Bu yazımızda SOLID prensiplerinden ilki olan Single Respoinsibility Principle(Tek Sorumluluk İlkesi) üzerinde duracağız. Daha önceki yazılarımdan birinde bu prensibi giriş seviyesinde inceleme şansı bulmuştuk. Bu yazıda biraz daha farklı bir yöntemle prensibe yaklaşmak istedim. Bir örnek kod üzerinde yapılmış ihlalleri ortaya koyup, bu soruna SRP ile çözüm bularak ilerlemeye çalışacağız.

public class StringCalculator
{
    public int Add(string number)
    {
       if (number.Length==0)
       {
           return 0;
       }
       return int.Parse(number);
   }
}

Bu sınıfa ait Add() metoduna baktığımızda metodun, Single Responsibility Principle bakımından bazı problemleri olduğu görüyoruz. Bu metodun çok fazla bilgiye sahip olduğu ortada. Metodun sahip olduğu bilgiler:

  • Ne zaman “0” döndüreceğini biliyor.
  • Döndürülecek default değerin “0” olduğunu biliyor.
  • Sayıların dönüştürme(parse) detaylarını biliyor.
 public class StringCalculator
 {
    private const int DefaultValue = 0;

    public int Add(string number)
    {
        if (isNumberEmpty(number))
        {
           return DefaultValue;
        }
        return parseSingleNumber(number);
    }

    private static bool isNumberEmpty(string number)
    {
        return number.Length == 0;
    }

    private static int parseSingleNumber(string number)
    {
       return int.Parse(number);
    }
}

Add() metodunun bilmemesi gereken durumları metodun dışına çıkardık. DefaultValue değeri değişeceği zaman veya number parametresinin boş(empty) olduğu durum kontrolünün değişeceği zaman sadece ilgili private metoda bakmamız yeterli olacaktır.

Bu durumda Add() metodunun, sadece algoritmayı bildiğini görebiliriz. Uygulama detaylarına dair hiçbir şey ile ilgilenmiyor. Yani metodun tek sorumluluğu sadece algoritmayı takip etmektir.

Metodalarımıza baktığımızda onları kitap gibi okuyabilmeliyiz. Ne yaptığını ve niyetini anlayabilmeliyiz. Bu şekilde çalışırsak gereksiz yorum satırlarıyla kod açıklaması yapmak zorunda kalmayız.

Bir başka ipucunda görüşmek dileğiyle.

Yazılım Sistemlerinde Soyutlamalar

13 Kas

Bir arabaya bindiğimizde aracı hareket ettirebilmek için gerekli bilgilere sahipsek aracı çalıştırıp yolumuza gidebiliriz. Aracı hareket ettirebilmemiz için bize öğretilen kurallar dizisi, aracın hareketini sağlayan mekanik aksamlardan arındırılmıştır. Yani şoför koltuğuna oturduğumuzda motordan tekerlere iletilen gücün, motora giden yakıtın veya aküde ki elektrik dağılımının yönetimi ile ilgilenmeyiz. Bu süreçler mühendisler tarafından tasarlanmış ve sürücüden soyutlanmıştır. Sürücüye sadece sisteme gerekli komutları vermek kalmıştır.

Arabaların donanımsal özelliklerinde yapılan değişiklikler sürücüleri etkilemez . Örneğin benzinli bir araç LPG yakıta dönüştürüldüğünde sürücüler, yeniden araç kullanma eğitimi almak zorunda kalmazlar.

Soyutlama işlemi, karmaşık sistemlerin uygulama alanındaki detaylarını kullanıcılardan gizlemek için uygulanır. Ayrıca karmaşık sistemlerin basitleştirilmesi ve kolayca anlaşılması için benimsenmiş ilkelerden biridir.

Yazılım sistemlerinde soyutlamalar mimari düzeyde yapılabildiği gibi kod düzeyinde de yapılabilmektedir.

Bir yazılım sisteminde mimari düzeyde yapılan soyutlamalar, yazılımı mimari katmanlar şeklinde sorumluluklara ayırarak yapılır. Böylece bir katman diğeri ile birlikte çalışır, fakat biri diğerinin çalışma alanını etkilemez. Hem de Separation of Concerns diye adlandırılan tasarım prensibine de uyulmuş olur. (Concern kelimesini burada “katmanlar” veya “işlevler” olarak değerlendirebiliriz).

Mimari Soyutlamalar
Mimari Seviyede Soyutlama

 

Yukarıdaki resimden de anlaşılacağı üzere, bir kullanıcı sadece Presentation Layer ile etkileşir. Bütün isteklerini bu katman üzeriden talep eder. Diğer katmanlardan haberdar değildir. Bu katmanların her birini farklı takımların geliştirdiğini varsayacak olursak, geliştiricilerin de sadece sorumlu olduğu katman içerisinde sorumlu olduğunu söyleyebiliriz. Bu şekilde izolasyon sağlanmış olur. Yazılım sistemlerinde kod seviyesinde yapılan soyutlamalar, nesneye yönelik programlama teknikleri ve tasarım desenlerinin doğru bir şekilde uygulanarak kod birimlerinin sınıflar şeklinde sorumluluklara ayrılması ile gerçekleştirilir. Örneğin üst seviye sınıfların alt seviye sınıflara bağımlı olması kodun gelişime kapalı olmasına yol açar.

 

Somut Nesne Kullanımı
Somut Nesne Kullanımı

 

Bunun yerine üst seviye sınıfları alt seviye sınıflara soyut nesnelerle bağlarız. Bu sayede üst seviye sınıflar alt seviye sınıfları tanımazlar. Interface, Abstract Class gibi soyut nesneleri tanırlar.

Soyut Nesne Kullanımı
Soyut Nesne Kullanımı

 

Bu örnek, kod seviyesinde yapılan soyutlamalara verilebilecek örneklerden sadece biridir. Kod seviyesinde yapılan soyutlamalarla nesneler arasındaki ilişkilerin ve bağımlılıkların düzenlenmesi de sağlanmış olur. Programcı, iş birimlerini somut sınıflara kodlarken, somut sınıfları soyut tipler aracılığı ile kullandırırlar. Bu sayede kullanıcılar, işleyiş detaylarını bilmeden ihtiyaçlarını karşılayabilirler.

Soyutlama kavramı, yazılım geliştirme sırasında oluşan bazı Anti-Pattern durumlarını da bertaraf eder. Aceleci ve plansız yaklaşımlar ile her şeyin birbirine girdiği projeler Anti-Pattern durumları doğurur. Yazılımda Anti-Pattern durumları, bir gömleğin ilk düğmesinin yanlış iliklenip diğerlerinin de yanlış iliklenmesine benzetirim. Yani başlangıçta düzensiz ve plansız başlanan işlerin zamanla, birikmiş hatalar yığınına dönüşeceği açıktır.

Anti-Pattern durumlardan arınmış projeler dileğiyle, tekrar görüşmek üzere.

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.

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.