Örneklerle S.O.L.I.D.

Örneklerle S.O.L.I.D.

SOLID bir yazılım geliştiricinin veya yazılım ekibinin OOP ile programlama yaparken uyması gereken kuralların bir araya getirildiği bir prensiptir.

Bu prensiplerle oluşturulan projenin daha modüler, kullanışlı, uzun ömürlü, genişletilebilir ve nesneler arası bağımlılıkların minimize edilmesi sağlanmaktadır.

SOLID’i oluşturan maddeleri açıklayalım;

Single Responsibility Principle (Tek Sorumluluk İlkesi)

Bu ilkede her classın farklı görevi vardır. Classlar, metodlar farklı görevler yaparlar. Yani görev ayrımı söz konusudur.

Hatalı Örnek:

    public class BadEmployeeProcessor     {          /*          * Buradaki sorun bir methodun hem çalışan kaydetmesi hem de loglama yapması.           *           * Yani ayrı ayrı işleri gerçekleştirmeliydi hem log hem veri eklenmesi yanlış!          */         public void InsertEmployee(Employee newEmployee)         {             StringBuilder sb = new StringBuilder();              try             {                 //Append silmeden sonuna ekleme işlemi yapar                 sb.Append(newEmployee.ID);                 sb.AppendLine(); // Alt Satıra geçmesi için...                 sb.Append(newEmployee.FirstName);                 sb.AppendLine();                 sb.Append(newEmployee.LastName);                 sb.AppendLine();                 sb.Append(newEmployee.HireDate);                  File.WriteAllText(@"C:\Employee\EmployeeData.txt", sb.ToString());                  sb = new StringBuilder();                 sb.Append("Kayıt Ekleme Tarihi: ");                 sb.Append(DateTime.Now.ToString());                 sb.AppendLine();                 sb.Append("Personel ID: ");                 sb.Append(newEmployee.FirstName).Append(" ").Append(newEmployee.LastName);                 File.WriteAllText(@"C:\Employee\Log.txt", sb.ToString());                 Console.WriteLine("Kayıt başarıyla eklendi!");             }             catch (Exception ex)             {                 sb = new StringBuilder();                 sb.Append("Hata Tarihi:");                 sb.Append(DateTime.Now.ToString());                 sb.AppendLine(); // Bir satır atla                 sb.Append("Hata Mesajı: ").Append(ex.Message);                 File.WriteAllText(@"C:\Employee\Log.txt", sb.ToString());                 Console.WriteLine("Hata!");               }           }     }

Yukarıdaki hatalı örnekte bir class içerisinde hem loglama işlemi hem de txt dosyasına veri ekleme işlemini tek class hatta tek fonksiyon gerçekleştirmiştir. Bu SRP’ye aykırı kod yazım şeklidir. Bu işleri ayırmalıyız.

Employee sınıfımız:

    public class Employee     {         public int ID { get; set; }         public string FirstName { get; set; }         public string LastName { get; set; }         public DateTime HireDate { get; set; }      }

Olması gereken kod yazım şekli:

Logger sınıfımız:

    public class Logger     {         public void LogFile(string filePath, string log)         {             File.WriteAllText(filePath, log);         }          public string BuildLog(string info)         {             StringBuilder sb = new StringBuilder();             sb.Append("Tarih: ");             sb.Append(DateTime.Now.ToString());             sb.AppendLine();             sb.Append("Bilgi: ").Append(info);              return sb.ToString();         }      }

GoodEmployeeProcessor Sınıfımız:

    public class GoodEmployeeProcessor     {         Logger logger;         string log;          public GoodEmployeeProcessor()         {             logger = new Logger();          }          public bool InsertEmployee(Employee newEmployee)         {             StringBuilder sb = new StringBuilder();              try             {                 //Append silmeden sonuna ekleme işlemi yapar                 sb.Append(newEmployee.ID);                 sb.AppendLine(); // Alt Satıra geçmesi için...                 sb.Append(newEmployee.FirstName);                 sb.AppendLine();                 sb.Append(newEmployee.LastName);                 sb.AppendLine();                 sb.Append(newEmployee.HireDate);                  log = logger.BuildLog(sb.ToString());                 logger.LogFile(@"C:\Employee\EmpData.txt",log);                  sb = new StringBuilder();                 sb.Append("Personel Eklendi!");                 sb.AppendLine();                 sb.Append("Personel ID: ");                 sb.Append(newEmployee.FirstName).Append(" ").Append(newEmployee.LastName);                  log = logger.BuildLog(sb.ToString());                 logger.LogFile(@"C:\Employee\EmpLog.txt", log);                  return true;             }             catch (Exception ex)             {                 sb = new StringBuilder();                                  sb.Append("Hata Mesajı: ").Append(ex.Message);                 sb.AppendLine(); // Bir satır atla                 sb.Append(ex.Message);                  log = logger.BuildLog(sb.ToString());                 logger.LogFile(@"C:\Employee\EmpLog.txt", log);                  //Console.WriteLine("Hata!");                  return false;             }           }       }

Program.cs

        GoodEmployeeProcessor processor = new GoodEmployeeProcessor();          Console.WriteLine(processor.InsertEmployee(newEmp)?"Başarılı":"Hata!");          Console.ReadLine();

Open/Closed Principle (Açık/Kapalı Prensibi)

OCP ilkesiyle oluşturulan mimaride sınıfların değiştirilmesi yasaklanmıştır. Yani sınıflar genişletilebilir ancak içeriği değiştirilmemelidir.

Bu prensibe aykırı örnek:

public enum CoffeeType {     Latte,     Espresso,     Macchiato } public class BadCoffee {     public double GetTotalPrice(double amount, CoffeeType coffeeType)     {         // Yeni bir kahve çeşidi eklenirse, yeni bir else if yapısı ekleneceğinden kodumuza müdahale etmiş olacağız. Class yapısı değişeceğinden Open-Closed prensibine ters düşmektedir. "Geliştirilebilir ancak değiştirilemez olarak tasarlanmalıydı!"          double totalPrice = 0;          if (coffeeType==CoffeeType.Espresso)         {             totalPrice = amount * 4.50;          }else if(coffeeType == CoffeeType.Latte)         {             totalPrice = amount * 5.25;          }else if(coffeeType==CoffeeType.Macchiato)         {             totalPrice = amount * 6.75;          }          return totalPrice;      } }

Program.cs (Her durumda da aynı):

class Program {     static void Main(string[] args)     {         GoodCoffee kahve1 = new Espresso();         GoodCoffee kahve2 = new Latte();         GoodCoffee kahve3 = new Macchiato();            double price1 = 0, price2=0, price3=0;         price1 = kahve1.GetTotalPrice(20);         price2 = kahve2.GetTotalPrice(20);         price3 = kahve3.GetTotalPrice(20);          Console.WriteLine("Toplam Tutar: "+price1);         Console.WriteLine("Toplam Tutar: "+price2);         Console.WriteLine("Toplam Tutar: "+price3);          Console.ReadLine();     } }

Olması gereken kod şekli:

    public abstract class GoodCoffee     {         public abstract double GetTotalPrice(double amount);      }

Bir abstract class oluşturarak bu classa abstract metot tanımladık ve eklenecek olan kahvelerin bu classı miras almasını sağladık bu şekilde ortak olarak Get TotalPrice metodu ezilmek zorunda bırakıldı ve böylece alta ne kadar sınıf eklenirse eklensin genişleme sağlanmış oldu ancak classlar için değişiklik yapma ihtiyacı engellenmiş oldu.

public class Espresso : GoodCoffee {     public override double GetTotalPrice(double amount)     {         //double totalPrice = amount * 4.50;          //return totalPrice;          return amount * 4.50;     } }
public class Latte : GoodCoffee {     public override double GetTotalPrice(double amount)     {         return amount * 5.25;     } }
    public class Macchiato : GoodCoffee     {         public override double GetTotalPrice(double amount)         {             return amount * 6.75;         }     }

Liskov Substitution Principle(Liskov İkame Prensibi)

İlke, bir üst sınıfın nesnelerinin, uygulamayı bozmadan alt sınıflarının nesneleriyle değiştirilebileceğini tanımlar. Bu, alt sınıflarınızın nesnelerinin, üst sınıfınızın nesneleriyle aynı şekilde davranmasını gerektirir.

Olumsuz örnek:

Bu örneğimizde ata sınıftaki değerler üzerinden çocuk sınıflar hesaplama yapmaktadır. Bu hesaplamada alan hesabından bahsedersek dikdörtgen için yükseklik ve genişliğin çarpılması gerekliyken, kare için ise tek kenarın karesinin alınması yeterlidir. Ancak örneğimizde hem yükseklik hem genişlik propertyleri bulunduğundan çeşitli tedbirler alsanız da hatalar meydana gelebilmektedir.

public class BadAreaCalculator {      public static double CalculateArea(BadRectangle rectangle)     {         return rectangle.Width * rectangle.Height;     }      public static double CalculateArea(BadSquare square)     {         return square.Width * square.Height;     }  }
public class BadRectangle {     public virtual int Height { get; set; }     public virtual int Width { get; set; }  }
public class BadSquare : BadRectangle {     int height;     int width;      public override int Height      {          get          {             return height;         }         set         {             height = value;             width = value;         }       }     public override int Width     {         get         {             return width;         }         set         {             height = value;             width = value;         }     } }

Olması gereken:

Yine ana çocuk yani kalıtım kullanmamız gerekiyorsa sınıflarımızın ihtiyaçları ve hesaplama şekilleri farklı olduğundan dolayı boş soyut bir sınıftan kalıtım işlemi gerçekleştirebiliriz.

public abstract class Shape { }
public class Square:Shape {     public int Width { get; set; }      public double SquareArea()     {         return Width * Width;     } }
class Rectangle:Shape {     public int Width { get; set; }     public int Height { get; set; }      public double RectangleArea()     {         return Width * Height;     } }

Interface Segregation Principle (Arayüz Ayrıştırma Prensibi)

ISP ile bir arayüzü implement eden sınıfın gereksiz metodları kullanması uygun olmadığı belirtilmiştir. Bu sebeple gerekirse arayüzler artırılmalı ve sınıflar ihtiyaç duyurulan arayüzleri implemente etmeli gereksiz metod, fonksiyon veya alanları kullanmak zorunda kalmamalıdır.

Aykırı örnek:

Bu örnekte bir kuş arayüzümüz var. Bu arayüzde işaretlenmiş de Fly yani uçmak ve Walk yürümek yetenekleri mevcut. Bu arayüzü kullanan sınıflar bu yetenekleri ezmek zorunda kalıyor. Mesela bir serçe hem uçup hem yürüyebilirken, tavuğun uçması mümkün olmamaktadır. Bu durumda tavuk olmayan yeteneği sınıfında kullanmak zorunda kalmaktadır.

public interface IBird {     string Fly();     string Walk(); }
public class BadHawk : IBird {     public string Fly()     {         return "Bu şahin uçabilir!";     }      public string Walk()     {         return "Bu şahin yürüyebilir!";     } }
public class BadChicken : IBird {     // Bu class'ta Fly yeteneği gereksiz olarak eklenmiştir. ISP'ye göre bir sınıfta kullanılmayacak property veya yetenekler eklenmemelidir. Dolayısıyla bu örnek ISP'ye ters düşmektedir.     public string Fly()     {         throw new NotImplementedException();     }      public string Walk()     {         return "Bu tavuk yürüyebilir";     } }

Olması gereken kullanım:

Uçabilen kuşlar, uçumayan kuşlar şeklinde farklı arayüzler geliştirerek yeteneğine göre sınıfların implementasyonlarını sağlayabiliriz.

public interface IFlightlessBird {     string Walk(); }
public interface IFlyingBird {     string Fly(); }
public class GoodChicken : IFlightlessBird {     public string Walk()     {         return "Bu tavuk yürüyebilir.";      } }
public class GoodHawk : IFlyingBird, IFlightlessBird {     public string Fly()     {         return "Bu şahin uçabilir.";     }      public string Walk()     {         return "Bu şahin yürüyebilir.";     } }

Dependency Inversion Principle (Bağımlılığı Ters Çevirme Prensibi)

DIP ilkesine göre üst sınıfın alt sınıflardan etkilenmemesi gerekmektedir. Yani üst sınıfın alt sınıflara bağlılığı bulunmaması tam tersine alt sınıfın üste bağımlığı söz konusu olmalıdır tabiki implementasyon söz konusuysa… Üst sınıf, alt sınıfın varlığı veya yokluğuyla güncelleme ihtiyacı duymamalıdır.

Aykırı kod örneği:

public class BadFish {     public string GetFishCookingInstructions()     {         throw new NotImplementedException();     } }
public class BadPoultry {     public string GetPoultryCookingInstructions()     {         // Pişirme talimatı kodları yer alabilir.         throw new NotImplementedException();     } }
public class BadRestaurant {     private BadFish tuna = new BadFish(); // tuna => ton balığı      private BadPoultry duck = new BadPoultry(); // duck => ördek      /*      * Bu örnekte BadRestaurant üst sınıf olmuştur ancak buradaki alt seviye classlardan method çektiği için bu classlara bağlıdır ve etkilenmektedir.      *       * Dependency Inversion Prensibine ters düşmektedir.      * Gerekli değişiklikler validated isimli klasör altında bulunan yapıdan incelenebilir.      */     public string GenerateInstructions()     {         return tuna.GetFishCookingInstructions() + " " +duck.GetPoultryCookingInstructions();     } }

Prensibe uygun hale getirilmiş olan kod yapımız:

public interface IProduct {     string CookingInstructions(); }
public class Fish : IProduct {     public string CookingInstructions()     {         throw new NotImplementedException();     }  }
public class Poultry : IProduct {     public string CookingInstructions()     {         throw new NotImplementedException();     } }
public class Restaurant {     private List<IProduct> _products;      public Restaurant(List<IProduct> products)     {         this._products = products;     }      public string GenerateInstructions()     {         string instructions = "";          foreach (var item in _products)         {             instructions += item.CookingInstructions();         }          return instructions;     }  }


Yayımlandı

kategorisi

yazarı:

Etiketler:

Yorumlar

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir