S.O.L.I.D (Nesne Yönelimli Programlama Prensipleri)

Öncelikle Bu Solid Açılımı Nedir ona Bakalım

SOLID
  1. S – Single-responsiblity principle (Tek sorumluluk prensibi) (SRP)
  2. O – Open-closed principle (Açıklık-kapalılık prensibi) (OSP)
  3. L – Liskov substitution principle (Liskov’un yer değiştirme prensibi) (LSP)
  4. I – Interface segregation principle (Arayüz Ayrımı prensibi) (ISS)
  5. D – Dependency invertion principle (Bağlılığı Tersine Çevirme prensibi) (DIP)

Solid’ in açılımını gördüğümüze göre artık tek tek bu başlıkları inceleyebiliriz…

  1. Single-responsiblity principle (Tek sorumluluk prensibi) (SRP)

SRP bize kısaca “Bir yazılım modülünün değişmesi için tek bir sebep olmalı” diyor. Yani bir modül sadece bir iş için var olmalıdır.

Modülden kast ettiğim; isterseniz sınıf olur, isterseniz interface ve onun implementasyonu olur isterseniz de method olur. Yani nesne yönelimli bir program adına aklınıza gelen her işlemi bu mantık çerçevesinde ele alabilirsiniz.

 Bu, sınıflarınızın yalnızca bir yöntem veya özellik içermesi gerektiği anlamına gelmiyor. Tek bir sorumlulukla ilgili oldukları sürece birçok üye olabilir.

public class UserService  
{  
   public void Register(string email, string password)  
   {  
      if (!ValidateEmail(email))  
         throw new ValidationException("Uygun bir E-posta değil");  
         var user = new User(email, password);  
  
         SendEmail(new MailMessage("mysite@mydomain.com", email) { Subject="Merhaba ..." });  
   }
   public virtual bool ValidateEmail(string email)  
   {  
     return email.Contains("@");  
   }  
   public bool SendEmail(MailMessage message)  
   {  
     _smtpClient.Send(message);  
   }  
} 

İyi görünüyor, ancak SRP’yi takip etmiyor. SendEmail ve ValidateEmail yöntemlerinin UserService sınıfı içinde ilgisi yoktur. Onu kıralım.

public class UserService  
{  
   EmailService _emailService;  
   DbContext _dbContext;  
   public UserService(EmailService aEmailService, DbContext aDbContext)  
   {  
      _emailService = aEmailService;  
      _dbContext = aDbContext;  
   }  
   public void Register(string email, string password)  
   {  
      if (!_emailService.ValidateEmail(email))  
         throw new ValidationException("Uygun bir E-posta değil");  
         var user = new User(email, password);  
         _dbContext.Save(user);  
         emailService.SendEmail(new MailMessage("myname@mydomain.com", email) {Subject="Merhaba Nasılsın!"});  
  
      }  
   }  
   public class EmailService  
   {  
      SmtpClient _smtpClient;  
   public EmailService(SmtpClient aSmtpClient)  
   {  
      _smtpClient = aSmtpClient;  
   }  
   public bool virtual ValidateEmail(string email)  
   {  
      return email.Contains("@");  
   }  
   public bool SendEmail(MailMessage message)  
   {  
      _smtpClient.Send(message);  
   }  
}

  2.Open-closed principle (Açıklık-kapalılık prensibi) (OSP)

OSP “Bir yazılım modülü / sınıfı uzatma için açık ve değişiklik için kapalı” diyor. Yani yazılım mesleğinin doğası gereği gelişime açık olması yatıyorsa bu da OSP’ nin önemini ortaya koyuyor.

Öyle bir proje tasarımı yapacağız ki gelecek olan yenilemeler var olan yapıyı bozmayacak, kodlarımızda herhangi bir değişiklik olmayacak ve var olan kod parçacıklarını ufak tefek eklemelerle muradımıza ermiş olacağız.

“Değişiklik için kapalı”, zaten bir sınıf geliştirdiğimizi ve birim testinden geçtiğini gösterir. Daha sonra böcek bulana kadar bunu değiştirmemeliyiz. Söylediği gibi, bir sınıf uzantılara açık olmalı, bunu yapmak için mirası kullanabiliriz. Tamam, hadi bir örneğe dalalım. 

Yükseklik ve Genişlik özelliklerine sahip bir Dikdortgen sınıfımız olduğunu varsayalım.

public class Dikdortgen{  
   public double Height {get;set;}  
   public double Wight {get;set; }  
} 

Bizim uygulamanın Dikdörtgenler koleksiyonunun toplam alanını hesaplaması için bir yeteneğe ihtiyacı var. Tek Sorumluluk İlkesini (SRP) zaten öğrendiğimizden, toplam alan hesaplama kodunu dikdörtgenin içine koymamız gerekmiyor. Böylece burada alan hesaplaması için başka bir sınıf oluşturdum.

public class AlanHesaplama {  
   public double ToplamAlan(Dikdortgen[] arrDikdortgen)  
   {  
      double alan;  
      foreach(var objDikdortgen in arrDikdortgen)  
      {  
         alan += objDikdortgen.Height * objDikdortgen.Width;  
      }  
      return alan;  
   }  
}   

SRP’yi ihlal etmeden uygulamamızı yaptık. Şimdilik sorun yok. Ancak sadece Dikdörtgenlerin değil, Çemberlerin alanlarını da hesaplayabilmek için uygulamamızı genişletebilir miyiz? Şimdi alan hesaplama ile ilgili bir sorunumuz var, çünkü daire alan hesaplama yapmanın yolu farklı. Önemli bir şey değil. ToplamAlan yöntemini biraz değiştirebiliriz, böylece bir nesne dizisini argüman olarak kabul edebilir. Döngüdeki nesne tipini kontrol ederiz ve nesne tipine göre alan hesaplamasını yaparız.

public class Dikdortgen{  
   public double Height {get;set;}  
   public double Wight {get;set; }  
}  
public class Daire{  
   public double Radius {get;set;}  
}  
public class AlanHesaplama  
{  
   public double ToplamAlan(object[] arrObjects)  
   {  
      double alan = 0;  
      Dikdortgen objDikdortgen;  
      Daire objDaire;  
      foreach(var obj in arrObjects)  
      {  
         if(obj is Dikdortgen)  
         {  
            objDikdortgen = (Dikdortgen)obj;  
            alan += obj.Height * obj.Width;  
         }  
         else  
         {  
            objDaire = (Daire)obj;  
            alan += objDaire.Radius * objDaire.Radius * Math.PI;  
         }  
      }  
      return alan;  
   }  
} 

Değişimi bitirdiğimize göre kodlarımızı inceleyelim. Burada başarıyla bizim uygulamamıza içine Daire sınıfını tanıttık. Üçgen ekleyebilir ve alanını Hesap Alanının ToplamAlan yöntemine bir “if” bloğu ekleyerek hesaplayabiliriz. Fakat her yeni bir şekil ortaya koyduğumuzda, ToplamAlan yöntemini değiştirmemiz gerekiyor. Bu nedenle, AlanHesaplama sınıfı değişiklik için kapalı değil. Bu durumdan kaçınmak için tasarımımızı nasıl yapabiliriz? Genel olarak bunu somut sınıflar kullanmak yerine, arayüzler veya soyut sınıflar gibi bağımlılıklar için soyutlamalara başvurarak yapabiliriz. Bu tür arayüzler bir kez geliştirildiklerinde sabitlenebilirler; İşlevsellik, arayüzleri uygulayan yeni sınıflar oluşturarak eklenebilir. Öyleyse bir arayüz kullanarak kodumuzu kıralım.

public abstract class Sekil  
{  
   public abstract double Alan();  
} 

Sekil sınıfından miras alarak, Dikdortgen ve Daire sınıfları şimdi şöyle görünür:

public class Dikdortgen: Sekil  
{  
   public double Height {get;set;}  
   public double Width {get;set;}  
   public override double Alan()  
   {  
      return Height * Width;  
   }  
}  
public class Daire: Sekil  
{  
   public double Radius {get;set;}  
   public override double Alan()  
   {  
      return Radius * Radus * Math.PI;  
   }  
} 

Her şekil kendi hesaplama fonksiyonunu içeren alan kodunu içerir ve AlanHesaplama sınıfımız öncekinden daha kolay olacaktır.

public class AlanHesaplama  
{  
   public double ToplamAlan(Shape[] arrShapes)  
   {  
      double alan=0;  
      foreach(var objSekil in arrSekiller)  
      {  
         alan += objSekil.Alan();  
      }  
      return alan;  
   }  
}  

Şimdi kodumuz hem SRP hem de OCP’yi takip ediyor. “Sekil” abstract sınıfından türeterek yeni bir şekil ortaya koyduğunuzda, “AlanHesaplama” sınıfını değiştirmeniz gerekmez. Harika Oldu. Öyle değil mi?

  3.Liskov substitution principle (Liskov’un yer değiştirme prensibi) (LSP)

yazı

Liskov Değişim Prensibi (LSP), “ebeveyn sınıfı yerine türetilmiş herhangi bir sınıfı kullanabilmeniz ve değiştirmeden aynı şekilde davranmanız gerektiğini” belirtir. Türetilmiş bir sınıfın ana sınıfın davranışını etkilememesini, diğer bir deyişle türetilmiş bir sınıfın temel sınıfının yerine geçmesi gerektiğini garanti eder. 

Bu ilke, Open Close Prensibinin sadece bir uzantısıdır ve yeni türetilmiş sınıfların davranışlarını değiştirmeden temel sınıfları genişletmesini sağlamamız gerektiği anlamına gelir.

Bir baba doktordur, oğlu ise kriketçi olmak ister. Yani burada oğul, ikisi de aynı aile hiyerarşisine ait olsa bile babasının yerini alamaz. 

Şimdi bir tasarımın LSP’yi nasıl ihlal edebileceğini öğrenmek için bir örneği ele alalım. Bir grup SQL dosyaları metni kullanarak verileri yönetmek için bir uygulama oluşturmamız gerektiğini varsayalım. Burada, bir grup SQL dosyasının metnini uygulama dizinine yüklemek ve kaydetmek için işlevsellik yazmamız gerekir. Bu yüzden SqlFile Sınıfı ile birlikte SQL dosya grubunun metninin yükünü ve tasarrufunu yöneten bir sınıfa ihtiyacımız var.

public class SqlFile  
{  
   public string FilePath {get;set;}  
   public string FileText {get;set;}  
   public string LoadText()  
   {  
      / * Sql dosyasından metin okuma kodu * /  
   }  
   public string SaveText()  
   {  
      / * Metni sql dosyasına kaydetme kodu * /  
   }  
}  
public class SqlFileManager  
{  
   public List<SqlFile> lstSqlFiles {get;set}  
  
   public string GetTextFromFiles()  
   {  
      StringBuilder objStrBuilder = new StringBuilder();  
      foreach(var objFile in lstSqlFiles)  
      {  
         objStrBuilder.Append(objFile.LoadText());  
      }  
      return objStrBuilder.ToString();  
   }  
   public void SaveTextIntoFiles()  
   {  
      foreach(var objFile in lstSqlFiles)  
      {  
         objFile.SaveText();  
      }  
   }  
}

İşlevsellik şimdilik iyi görünüyor. Bir süre sonra müşterimiz bize uygulama klasöründe birkaç salt okunur dosya olabileceğini söyleyebilir, bu yüzden onlardan tasarruf etmeye çalıştığımızda akışı kısıtlamamız gerekir.

Bunu, “SqlFile” sınıfını alan bir “ReadOnlySqlFile” sınıfı oluşturarak yapabiliriz ve ReadTextIntoFiles () yöntemini, ReadOnlySqlFile örneklerinde SaveText () yönteminin çağrılmasını engellemek için bir koşul getirerek değiştirmemiz gerekir.

public class SqlFile  
{  
   public string LoadText()  
   {  
   / * Sql dosyasından metin okuma kodu * /  
   }  
   public void SaveText()  
   {  
      / * Metni sql dosyasına kaydetme kodu * /  
   }  
}  
public class ReadOnlySqlFile: SqlFile  
{  
   public string FilePath {get;set;}  
   public string FileText {get;set;}  
   public string LoadText()  
   {  
   / * Sql dosyasından metin okuma kodu * /  
   }  
   public void SaveText()  
   {  
      / * Uygulama akışı kaydetmeye çalıştığında bir istisna at. * /  
      throw new IOException("Can't Save");  
   }  
}   

İstisnalardan kaçınmak için “SqlFileManager” ı döngüye bir koşul ekleyerek değiştirmemiz gerekiyor.

public class SqlFileManager  
{  
   public List<SqlFile? lstSqlFiles {get;set}  
   public string GetTextFromFiles()  
   {  
      StringBuilder objStrBuilder = new StringBuilder();  
      foreach(var objFile in lstSqlFiles)  
      {  
         objStrBuilder.Append(objFile.LoadText());  
      }  
      return objStrBuilder.ToString();  
   }  
   public void SaveTextIntoFiles()  
   {  
      foreach(var objFile in lstSqlFiles)  
      {  
         
         // Geçerli dosya nesnesinin sadece okunup okunmadığını kontrol edin. Evet ise, aramayı atlayın  
         // İstisnaları atlamak için SaveText () metodu. 
  
         if(! objFile is ReadOnlySqlFile)  
         objFile.SaveText();  
      }  
   }  
}   

Burada, istisnayı önlemek için örneğin ReadOnlySqlFile örneğinin olup olmadığını belirlemek için SqlFileManager sınıfındaki SaveTextIntoFiles () yöntemini değiştirdik. Bu ReadOnlySqlFile sınıfını, SqlFileManager kodunu değiştirmeden, üst öğesinin yerine kullanabilirsiniz. Dolayısıyla bu tasarımın LSP’yi takip etmediğini söyleyebiliriz. Bu tasarımın LSP’yi takip etmesini sağlayalım. Burada SqlFileManager sınıfını diğer tüm bloklardan bağımsız kılacak arayüzleri tanıtacağız.

public interface IReadableSqlFile  
{  
   string LoadText();  
}  
public interface IWritableSqlFile  
{  
   void SaveText();  
} 

Şimdi IReadableSqlFile, salt okunur dosyalardan yalnızca metni okuyan ReadOnlySqlFile sınıfı aracılığıyla uygularız.

public class ReadOnlySqlFile: IReadableSqlFile  
{  
   public string FilePath {get;set;}  
   public string FileText {get;set;}  
   public string LoadText()  
   {  
      / * Sql dosyasından metin okuma kodu * /  
   }  
}   

Burada hem IWritableSqlFile hem de IReadableSqlFile dosyalarını SqlFile sınıfında uygulayabilir ve bunları okuyabilir ve yazabiliriz. 

public class SqlFile: IWritableSqlFile,IReadableSqlFile  
{  
   public string FilePath {get;set;}  
   public string FileText {get;set;}  
   public string LoadText()  
   {  
      / * Sql dosyasından metin okuma kodu * /  
   }  
   public void SaveText()  
   {  
      / * Metni sql dosyasına kaydetme kodu * /  
   }  
}  

Şimdi SqlFileManager sınıfının tasarımı şöyle olur:

public class SqlFileManager  
{  
   public string GetTextFromFiles(List<IReadableSqlFile> aLstReadableFiles)  
   {  
      StringBuilder objStrBuilder = new StringBuilder();  
      foreach(var objFile in aLstReadableFiles)  
      {  
         objStrBuilder.Append(objFile.LoadText());  
      }  
      return objStrBuilder.ToString();  
   }  
   public void SaveTextIntoFiles(List<IWritableSqlFile> aLstWritableFiles)  
   {  
   foreach(var objFile in aLstWritableFiles)  
   {  
      objFile.SaveText();  
   }  
   }  
}   

Burada GetTextFromFiles () yöntemi yalnızca IReadOnlySqlFile arayüzünü uygulayan sınıfların örneklerinin listesini alır. Bu, SqlFile ve ReadOnlySqlFile sınıfı örnekleri anlamına gelir. Ve SaveTextIntoFiles () yöntemi, yalnızca IWritableSqlFiles arabirimini uygulayan sınıfın liste örneklerini, diğer bir deyişle bu durumda SqlFile örneklerini alır. Şimdi tasarımımızın LSP’yi takip ettiğini söyleyebiliriz. Ve sorunu, soyutlamayı ve sorumluluk ayrıştırma yöntemini tanımlayan (ISS) Arabirim ayırma ilkesini kullanarak düzelttik.

  4.Interface segregation principle (Arayüz Ayrımı prensibi) (ISS)

Sınıflar gibi, her arayüzün belirli bir amacı / sorumluluğu olmalıdır (SRP’ye bakın). Nesneniz bu amacı paylaşmadığın da bir arayüz uygulamak zorunda kalmamalısınız. Arayüz ne kadar büyük olursa, tüm uygulayıcıların yapamayacağı yöntemler içerir. Arayüz Ayrıştırma İlkesinin özü budur. ISS’yi kıran bir örnekle başlayalım. TeamLead’in büyük bir görevi daha küçük görevlere böldüğü ve programlayıcılarına atadığı ya da doğrudan üzerinde çalışabileceği TeamLead ve Programmer gibi roller içeren bir Bilgi Teknolojileri firması için bir sistem kurmamız gerektiğini varsayalım. 

Spesifikasyonlara dayanarak, onu uygulamak için bir arayüz ve TeamLead sınıfı oluşturmamız gerekir.

public Interface ILead  
{  
   void CreateSubTask();  
   void AssginTask();  
   void WorkOnTask();  
}  
public class TeamLead : ILead  
{  
   public void AssignTask()  
   {  
      // Bir görevi atamak için kod.  
   }  
   public void CreateSubTask()  
   {  
      // Bir alt görev oluşturmak için kod  
   }  
   public void WorkOnTask()  
   {  
      // Uygulanacak kod atanan görevi gerçekleştirir.  
   }  
}   

Tasarım şimdilik iyi görünüyor. Daha sonra TeamLead’e görevler atayan ve görevler üzerinde çalışmayacak olan Yönetici gibi bir başka rol sisteme dahil edildi. Aşağıdaki gibi, bir ILead arabirimini doğrudan Manager sınıfına uygulayabilir miyiz?

public class Manager: ILead  
{  
   public void AssignTask()  
   {  
      // Bir görevi atamak için kod.  
   }  
   public void CreateSubTask()  
   {  
      // Bir alt görev oluşturmak için kod.  
   }  
   public void WorkOnTask()  
   {  
      throw new Exception("Yönetici Görevde çalışamaz");  
   }  
}  

Yönetici bir görev üzerinde çalışamadığından ve aynı zamanda hiç kimse Yöneticiye görev atayamadığından, bu WorkOnTask () Yöneticisi sınıfında olmamalıdır. Fakat bu sınıfı ILead arayüzünden uyguluyoruz, somut bir Yöntem sunmamız gerekiyor. Burada Manager sınıfını bir amaç olmadan WorkOnTask () yöntemini uygulamaya zorluyoruz. Bu yanlış. Tasarım ISS’yi ihlal ediyor. Tasarımı düzeltelim. 

Üç görevimiz olduğundan, yalnızca görevleri bölen ve atayabilecek bir yönetici, 2. Görevleri bölen ve atayabilen ve üzerinde çalışabilecek TeamLead, 3. Sadece görevler üzerinde çalışabilen programcı, ILead arayüzünü ayırarak sorumlulukları paylaşın. WorkOnTask () için bir sözleşme sağlayan bir arayüz.

public interface IProgrammer  
{  
   void WorkOnTask();  
}   

Görevleri yönetmek için sözleşmeler sağlayan bir arayüz: 

public interface ILead  
{  
   void AssignTask();  
   void CreateSubTask();  
} 

Sonra uygulama:

public class Programmer: IProgrammer  
{  
   public void WorkOnTask()  
   {  
      // Görev üzerinde çalışmak için uygulanacak kod.  
   }  
}  
public class Manager: ILead  
{  
   public void AssignTask()  
   {  
      // Bir Görev Atama Kodu  

   public void CreateSubTask()  
   {  
   // Bir görevden alt takma oluşturma kodu.  
   }  
}  

TeamLead görevleri yönetebilir ve gerekirse üzerinde çalışabilir. O zaman TeamLead sınıfı hem IProgrammer hem de ILead arabirimlerini implement etmelidir.

public class TeamLead: IProgrammer, ILead  
{  
   public void AssignTask()  
   {  
      // Bir görevi atamak için kod.  
   }  
   public void CreateSubTask()  
   {  
      // Bir görevden alt görev oluşturma kodu.  
   }  
   public void WorkOnTask()  
   {  
      // Görev üzerinde çalışmak için uygulanacak kod.  
   }  
}

Burada sorumlulukları / amaçları birbirinden ayırdık ve çoklu arayüzlere dağıttık ve iyi bir soyutlama işlevselliği kazandırmış olduk uygulamamıza. 

4.Dependency invertion principle (Bağlılığı Tersine Çevirme prensibi) (DIP)

Bağımlılık İnversiyon Prensibi (DIP), yüksek seviye modüllerin / sınıfların düşük seviye modüllere / sınıflara bağlı olmaması gerektiğini belirtir. Her ikisi de soyutlamalar bağlı olmalıdır. İkincisi, soyutlamalar ayrıntılara bağlı olmamalıdır. Detaylar soyutlamaya bağlı olmalıdır. 

Üst düzey modüller / sınıflar bir sistemde (uygulamada) iş kurallarını veya mantığı uygular. Düşük seviyeli modüller / sınıflar daha ayrıntılı işlemlerle uğraşır, başka bir deyişle veritabanlarına bilgi yazma veya işletim sistemine veya hizmetlere mesaj iletme ile ilgilenebilirler.

Düşük seviyeli modüllere / sınıflara veya bazı diğer sınıflara bağımlı olan ve etkileşime girdiği diğer sınıflar hakkında çok şey bilen üst düzey bir modül / sınıfın sıkı bir şekilde birleştiği söylenir. Bir sınıf, başka bir sınıfın tasarımını ve uygulamasını açıkça bildiğinde, bir sınıftaki değişikliklerin diğer sınıfı bozması riski artar. Bu yüzden bu üst ve alt seviye modüllerin / sınıfın elimizden geldiğince gevşek bir şekilde bağlı kalmasını sağlamalıyız. Bunu yapmak için, ikisini de birbirlerini tanımak yerine soyutlamalara bağımlı hale getirmeliyiz. Bir örnek ile başlayalım. 

İstisna yığını izlerini bir dosyaya kaydeden bir hata günlüğü modülünde çalışmamız gerektiğini varsayalım. Basit değil mi? Bir yığın izini bir dosyaya kaydetmek için işlevsellik sağlayan sınıflar aşağıdadır.

public class FileLogger  
{  
   public void LogMessage(string aStackTrace)  
   {  
      // yığın izini bir dosyaya kaydetmek için kod.  
   }  
}  
public class ExceptionLogger  
{  
   public void LogIntoFile(Exception aException)  
   {  
      FileLogger objFileLogger = new FileLogger();  
      objFileLogger.LogMessage(GetUserReadableMessage(aException));  
   }  
   private GetUserReadableMessage(Exception ex)  
   {  
      string strMessage = string. Empty;  
      // İstisna'nın yığın izini ve mesajını kullanıcı tarafından okunabilir formata dönüştürecek kod.  
      ....  
      ....   
      return strMessage;  
   }  
}  

Bir istemci sınıfı birçok dosyadan veriyi bir veritabanına aktarır.

public class DataExporter  
{  
   public void ExportDataFromFile()  
   {  
   try {  
      // Mesajları veritabanına yazacak kod.  
   }  
   catch(Exception ex)  
   {  
      new ExceptionLogger().LogIntoFile(ex);  
   }  
}  
}  

İyi görünüyor. Uygulamamızı müşteriye gönderdik. Ancak müşterimizin, bir istisnası meydana gelirse, bu yığın izini bir veri-tabanın da depolamak ister. sorun değil. Bunu da uygulayabiliriz. Burada, yığın izlemesini veri-tabanına kaydetme işlevselliği sağlayan bir sınıf daha eklememiz ve yığın izlemesini günlüğe kaydetmemiz için yeni sınıfımızla etkileşime girmesi için ExceptionLogger’ da ek bir yöntem eklememiz gerekir.

public class DbLogger  
{  
   public void LogMessage(string aMessage)  
   {  
      // yığın izini bir dosyaya kaydetmek için kod.  
   }  
}  
public class FileLogger  
{  
   public void LogMessage(string aStackTrace)  
   {  
      // yığın izini bir dosyaya kaydetmek için kod.  
   }  
}  
public class ExceptionLogger  
{  
   public void LogIntoFile(Exception aException)  
   {  
      FileLogger objFileLogger = new FileLogger();  
      objFileLogger.LogMessage(GetUserReadableMessage(aException));  
   }  
   public void LogIntoDataBase(Exception aException)  
   {  
      DbLogger objDbLogger = new DbLogger();  
      objDbLogger.LogMessage(GetUserReadableMessage(aException));  
   }  
   private string GetUserReadableMessage(Exception ex)  
   {  
      string strMessage = string.Empty;  
      // İstisna'nın yığın izini ve mesajını kullanıcı tarafından okunabilir formata dönüştürecek kod.  
      ....  
      ....  
      return strMessage;  
   }  
}  
public class DataExporter  
{  
   public void ExportDataFromFile()  
   {  
      try {  
         //code to export data from files to database.  
      }  
      catch(IOException ex)  
      {  
         new ExceptionLogger().LogIntoDataBase(ex);  
      }  
      catch(Exception ex)  
      {  
         new ExceptionLogger().LogIntoFile(ex);  
      }  
   }  
}   

Şimdilik iyi görünüyor. Ancak müşteri ne zaman yeni bir kayıt cihazı eklemek isterse, yeni bir yöntem ekleyerek ExceptionLogger’ı değiştirmemiz gerekir. Bir süre sonra bunu yapmaya devam edersek, bir mesajın çeşitli hedeflere kaydedilmesini sağlayan işlevselliği sağlayan şişman bir ExceptionLogger sınıfını göreceğiz. Bu sorun neden ortaya çıkıyor? Çünkü ExceptionLogger, istisnayı günlüğe kaydetmek için doğrudan üst düzey FileLogger ve DbLogger sınıflarıyla bağlantı kurar. Bu ExceptionLogger sınıfının bu sınıfla gevşek bir şekilde birleştirilebilmesi için tasarımı değiştirmemiz gerekiyor. Bunu yapmak için aralarında bir soyutlama yapmamız gerekir, böylece ExcetpionLogger düşük seviyeli sınıflara doğrudan bağlı kalmak yerine istisnayı günlüğe kaydetmek için soyutlamayla bağlantı kurabilir.

public interface ILogger  
{  
   void LogMessage(string aString);  
}

Şimdi düşük seviyeli sınıflarımızın bu arayüzü kullanması gerekiyor.

public class DbLogger: ILogger  
{  
   public void LogMessage(string aMessage)  
   {  
      // Mesajları veritabanına yazacak kod.  
   }  
}  
public class FileLogger: ILogger  
{  
   public void LogMessage(string aStackTrace)  
   {  
      // yığın izini bir dosyaya kaydetmek için kod.  
   }  
} 

Şimdi, ExceptionLogger’ın düşük seviyeli FileLogger ve EventLogger sınıflarıyla gevşek bir şekilde birleşmesini sağlamak için ExcetpionLogger sınıfından DataExporter sınıfına girme seviyesini düşürüyoruz. Bunu yaparak, DataExporter sınıfına, ortaya çıkan istisna temelinde ne tür bir Kaydedici’nin aranması gerektiğine karar vermek için hüküm veriyoruz.

public class ExceptionLogger  
{  
   private ILogger _logger;  
   public ExceptionLogger(ILogger aLogger)  
   {  
      this._logger = aLogger;  
   }  
   public void LogException(Exception aException)  
   {  
      string strMessage = GetUserReadableMessage(aException);  
      this._logger.LogMessage(strMessage);  
   }  
   private string GetUserReadableMessage(Exception aException)  
   {  
      string strMessage = string.Empty;  
      //code to convert Exception's stack trace and message to user readable format.  
      ....  
      ....  
      return strMessage;  
   }  
}  
public class DataExporter  
{  
   public void ExportDataFromFile()  
   {  
      ExceptionLogger _exceptionLogger;  
      try {  
         // dosyalardan veritabanına veri aktarmak için kod.  
      }  
      catch(IOException ex)  
      {  
         _exceptionLogger = new ExceptionLogger(new DbLogger());  
         _exceptionLogger.LogException(ex);  
      }  
      catch(Exception ex)  
      {  
         _exceptionLogger = new ExceptionLogger(new FileLogger());  
         _exceptionLogger.LogException(ex);  
      }  
   }  
}

Düşük seviyeli sınıflara olan bağımlılığı başarıyla kaldırdık. Bu ExceptionLogger, yığın izlemesini kaydetmek için FileLogger ve EventLogger sınıflarına bağlı değildir. Herhangi bir yeni günlük işlevi için ExceptionLogger kodunu değiştirmemize gerek yok. ILogger arayüzünü uygulayan ve DataExporter sınıfının ExportDataFromFile yöntemine başka bir catch bloğu eklemesi gereken yeni bir günlük sınıfı oluşturmamız gerekiyor.

public class EventLogger: ILogger  
{  
   public void LogMessage(string aMessage)  
   {  
      // Sistemin olay görüntüleyicisine mesaj yazabilme kodu.  
   }  
} 

Ve DataExporter sınıfına aşağıdaki gibi bir koşul eklememiz gerekir:

public class DataExporter  
{  
   public void ExportDataFromFile()  
   {  
      ExceptionLogger _exceptionLogger;  
      try {  
         // dosyalardan veritabanına veri aktarmak için kod.  
      }  
      catch(IOException ex)  
      {  
         _exceptionLogger = new ExceptionLogger(new DbLogger());  
         _exceptionLogger.LogException(ex);  
      }  
      catch(SqlException ex)  
      {  
         _exceptionLogger = new ExceptionLogger(new EventLogger());  
         _exceptionLogger.LogException(ex);  
      }  
      catch(Exception ex)  
      {  
         _exceptionLogger = new ExceptionLogger(new FileLogger());  
         _exceptionLogger.LogException(ex);  
      }  
   }  
}   

yazı

İyi görünüyor. Fakat bağımlılığı burada DataExporter sınıfının catch bloklarında gösterdik. Evet, birisi işi yapmak için ExceptionLogger’a gerekli nesneleri sağlama sorumluluğunu almalıdır. 

Bunu gerçek bir dünya örneği ile açıklayayım. Farz edelim ki belirli ölçülerde bir ahşap sandalye ve o sandalyeyi yapmak için kullanılacak odun türünü kullanmak istiyoruz. Öyleyse, ölçümler ve odun hakkında karar vermeyi marangozlara bırakamayız. Burada işi, araçlarıyla gereksinimlerimize dayanarak bir sandalye yapmak ve iyi bir sandalye yapmak için ona özellikleri sağlıyoruz.

Peki, tasarımdan elde ettiğimiz fayda nedir? Kesinlikle bununla bir avantajımız var. Yeni bir günlük kaydı işlevi eklememiz gerektiğinde hem DataExporter sınıfını hem de ExceptionLogger sınıfını değiştirmemiz gerekiyor. Ancak güncellenmiş tasarımda, yeni istisna günlüğü özelliği için yalnızca başka bir kilitleme bloğu eklememiz gerekir. Kavrama doğal olarak kötü değildir. Bir miktar bağlantınız yoksa, yazılımınız sizin için hiçbir şey yapmaz. Yapmamız gereken tek şey sistemi, gereklilikleri ve çevreyi iyi anlamak ve DIP’nin izleneceği alanları bulmak. 

Bu ilkeleri kullanarak derli toplu, okunabilir ve bakımı kolay bir uygulama oluşturabileceğimiz sonucuna varabiliriz.Burada bazı şüpheleriniz olabilir. Evet, kod miktarı hakkında. Bu ilkeler nedeniyle, uygulamalarımızda kod daha büyük olabilir. Bu ilkeleri izleyerek elde ettiğimiz kalite ile karşılaştırmanız gerekir. 

Umarım Bu makalede sizlere bir şeyler katabilmişimdir. Okuduğunuz için teşekkür ediyorum.

You may also like...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir