NEDİR BU SOLID PRENSİPLERİ?(RESİMLİ ANLATIM)

Buse Yalçın
6 min readAug 3, 2021

--

Merhabalar bu yazımda biz yazılımcılar için hayati önem taşıyan SOLID prensiplerinden bahsedeceğim.

SOLID ilkeleri ilk olarak 2000 yılında ünlü Bilgisayar Bilimcisi Robert J. Martin (Bob Amca olarak bilinir) tarafından tanıtıldı. Ancak SOLID kısaltması daha sonra Michael Feathers tarafından tanıtıldı. SOLID ilk beş nesne yönelimli tasarım (object-oriented design/OOD) ilkesinin kısaltmasıdır.

Bob Amca aynı zamanda çok satan Clean Code and Clean Architecture kitaplarının yazarıdır ve “Agile Alliance” ın katılımcılarından biridir .

Bu nedenle, tüm bu temiz kodlama, nesne yönelimli mimari ve tasarım örüntüleri kavramlarının bir şekilde bağlantılı ve birbirini tamamlayıcı olması şaşırtıcı değildir.

Hepsi aynı amaca hizmet ediyor:

“Birçok geliştiricinin işbirliği içinde çalışabileceği anlaşılır, okunabilir ve test edilebilir kodlar oluşturmak için.”

Her prensibe tek tek bakalım. SOLID kısaltmasının ardından bunlar:

  • S — Single-Responsiblity Principle (Tek Sorumluluk İlkesi)
  • O — Open-Closed Principle (Açık-kapalı İlkesi)
  • L — Liskov Substitution Principle (Liskov İkame İlkesi)
  • I — Interface Segregation Principle (Arayüz Ayrım İlkesi)
  • D — Dependency Inversion Principle (Bağımlılığı Ters Çevirme İlkesi)

S — Single-Responsiblity Principle (Tek Sorumluluk İlkesi):

Bir Sınıfın birçok sorumluluğu varsa, hata olasılığını artırır çünkü sorumluluklarından birinde değişiklik yapmak, siz bilmeden diğerlerini etkileyebilir.

Bu ilke, değişikliğinizin bir sonucu olarak hatalar ortaya çıkarsa, diğer ilgisiz davranışları etkilemeyecek şekilde davranışları ayırmayı amaçlamaktadır.

Şimdi aşağıdaki örnekle daha iyi anlayalım;

public class Invoice {

private Book book;
private int quantity;
private double discountRate;
private double taxRate;
private double total;

public Invoice(Book book, int quantity, double discountRate, double taxRate) {
this.book = book;
this.quantity = quantity;
this.discountRate = discountRate;
this.taxRate = taxRate;
this.total = this.calculateTotal();
}

public double calculateTotal() {
double price = ((book.price - book.price * discountRate) * this.quantity);

double priceWithTaxes = price * (1 + taxRate);

return priceWithTaxes;
}

public void printInvoice() {
System.out.println(quantity + "x " + book.name + " " + book.price + "$");
System.out.println("Discount Rate: " + discountRate);
System.out.println("Tax Rate: " + taxRate);
System.out.println("Total: " + total);
}

public void saveToFile(String filename) {
// Creates a file with given name and writes the invoice
}

}

Burada sınıf tasarımında ki hatayı anlamak için kendinize biraz zaman verin. Hangi ilkenin ihlali söz konusu?

Evet.. Sınıfımız, Tek Sorumluluk İlkesini çeşitli şekillerde ihlal ediyor. Peki bu durumda ne mi yapıyoruz?

InvoicePrinter ve InvoicePersistence olmak üzere 2 sınıf oluşturup metodları taşıyoruz .

public class InvoicePrinter {
private Invoice invoice;

public InvoicePrinter(Invoice invoice) {
this.invoice = invoice;
}

public void print() {
System.out.println(invoice.quantity + "x " + invoice.book.name + " " + invoice.book.price + " $");
System.out.println("Discount Rate: " + invoice.discountRate);
System.out.println("Tax Rate: " + invoice.taxRate);
System.out.println("Total: " + invoice.total + " $");
}
}

public class InvoicePersistence {
Invoice invoice;

public InvoicePersistence(Invoice invoice) {
this.invoice = invoice;
}

public void saveToFile(String filename) {
// Creates a file with given name and writes the invoice
}
}

Artık sınıf yapımız Tek Sorumluluk İlkesine uyan hale geldi.

O — Open-Closed Principle (Açık-kapalı İlkesi):

Nesneler veya varlıklar genişletme için açık olmalı ancak değişiklik için kapalı olmalıdır.

Türkçe çevirisi “Açık/Kapalı” olan prensip, projede geliştirilen nesnelerin geliştirilmeye açık ama değişime kapalı olmaları gerektiğini ifade eder. Yani bir nesne davranışını değiştirmeden yeni özellikler kazabiliyor olmalıdır. Bu prensip, sürdürülebilir ve tekrar kullanılabilir yapıda kod yazmanın temelini oluşturur. Bir örnekle daha iyi anlamaya çalışalım.

class Musteri{private int _MusType;
public int MusType{
get { return _CustType; }set { _CustType = value; }
}
public double getIndirim(double ToplamSatis){if (_MusType == 1){return TotalSales - 100;} else {return TotalSales - 50;}}}

Yukarıdaki ve aşağıdaki kodları karşılaştırdığımız da Açık-kapalı İlkesi’ni çok daha iyi anlamış oluyoruz.

class Musteri{public virtual double getIndirim(double ToplamSatis){return ToplamSatis;}}class GumusMusteri : Musteri{public override double getIndirim(double ToplamSatis){return base.getIndirim(ToplamSatis) - 50;}}class AltinMusteri : Musteri{public override double getIndirim(double ToplamSatis){return base.getIndirim(ToplamSatis) - 100;}}

L — Liskov Substitution Principle(Liskov İkame İlkesi):

Bir programdaki nesneler, o programın doğruluğunu değiştirmeden alt türlerinin örnekleriyle değiştirilebilir olmalıdır. Başka bir deyişle, her alt sınıf, alt sınıfa özgü tüm yeni davranışlarla birlikte temel sınıftaki tüm davranışları korumalıdır. Alt sınıf, aynı istekleri işleyebilmeli ve üst sınıfıyla aynı görevleri tamamlayabilmelidir. LSP’nin avantajı, aynı türdeki tüm alt sınıfların tutarlı bir kullanımı paylaştığı için yeni alt sınıfların geliştirilmesini hızlandırmasıdır.

I — Interface Segregation Principle (Arayüz Ayırma İlkesi):

İsteme özel birçok arayüz, tek bir genel amaçlı arayüzden daha iyidir. ISP’de sınıflar kullanmadıkları davranışları içermesi istenmez. Aslında, bu durum ilk SOLID ilkemizle de ilgilidir. Çünkü, bu ilke programa doğrudan katkıda bulunmayan tüm değişkenleri, metotları veya davranışları bir sınıftan çıkarır. ISP ise metotların daha spesifik metotlara dönüştürülmesidir. Bu sayede,

  • Daha az kod taşıyan metotlar elde edilir. Kodun ihtiyaç durumunda güncellemesi hızlanır.
  • Davranıştan bir metot sorumlu olduğu için davranışta karşılaşılan problem hızlı çözülür.

Bir örnek üzerinden inceleyeceyecek olursak;

public interface ICalisan{string ID { get; set; }string Name { get; set; }string Email { get; set; }float MonthlySalary { get; set; }float OtherBenefits { get; set; }float HourlyRate { get; set; }float HoursInMonth { get; set; }float CalculateNetSalary();float CalculateWorkedSalary();}public class TamZamanliPersonel : ICalisan{public string ID { get; set; }public string Name { get; set; }public string Email { get; set; }public float MonthlySalary { get; set; }public float OtherBenefits { get; set; }public float HourlyRate { get; set; }public float HoursInMonth { get; set; }public float CalculateNetSalary() => MonthlySalary + OtherBenefits;public float CalculateWorkedSalary() => throw new NotImplementedException();}public class SozlesmeliPersonel : ICalisan{public string ID { get; set; }public string Name { get; set; }public string Email { get; set; }public float MonthlySalary { get; set; }public float OtherBenefits { get; set; }public float HourlyRate { get; set; }public float HoursInMonth { get; set; }public float CalculateNetSalary() => throw new NotImplementedException();public float CalculateWorkedSalary() => HourlyRate * HoursInMonth;}

ICalisan Interface birçok metot içeriyor ve bu metotları hepsi sınıflara aktarılıyor ve iki farklı personel için metotlarda çakışıyor. Aslında bazı metotlar personel grupları için gereksizdir. Bu metotlar aşağıdaki gibi daha düzgün bir hale getirilebilir.

public interface IBaseCalisan {string ID { get; set; }string Name { get; set; }string Email { get; set; }}public interface ITamZamanliCalisanUcret : IBaseCalisan {float MonthlySalary { get; set; }float OtherBenefits { get; set; }float CalculateNetSalary();}public interface ISozlesmeliCalisanUcret : IBaseCalisan {float HourlyRate { get; set; }float HoursInMonth { get; set; }float CalculateWorkedSalary();}public class TamZamanliPersonel : ITamZamanliCalisanUcret {public string ID { get; set; }public string Name { get; set; }public string Email { get; set; }public float MonthlySalary { get; set; }public float OtherBenefits { get; set; }public float CalculateNetSalary() => MonthlySalary + OtherBenefits;}public class SozlesmeliPersonel : ISozlesmeliCalisanUcret {public string ID { get; set; }public string Name { get; set; }public string Email { get; set; }public float HourlyRate { get; set; }public float HoursInMonth { get; set; }public float CalculateWorkedSalary() => HourlyRate * HoursInMonth;}

Ücret hesaplama metotları tam zamanlı ve sözleşmeli personel için ayrılmıştır. Bu sayede daha rahat okunur ve değiştirilebilir bir kod elde edilmiştir.

D — Dependency Inversion Principle (Bağımlılığı Tersine Çevirme İlkesi):

Bu prensibe göre; alt sınıflarda yapılan değişiklikler üst sınıfları etkilememelidir yani sınıflar arası bağımlılıklar olabildiğince az olmalıdır ve özellikle üst seviye sınıflar, alt seviye sınıflara bağımlı olmamalıdır. Peki burada ne yapmalıyız? Burada yüksek seviye sınıf ile düşük seviye sınıf arasında bir soyutlama katmanı oluşturarak her iki sınıfı da soyut kavramlar üzerinden yönetmeliyiz.

Bu yazımda anlatacaklarım şimdilik bu kadar. Bilgi paylaştıkça çoğalır. Okudukça, öğrendikçe paylaşmaya devam edeceğim 😊

Yazımı faydalı bulduysanız aşağıda ki alkış butonuna çökmeye ne dersiniz 👏 👼

--

--

Buse Yalçın

Computer Engineer ⚪️ Backend Developer ⚪️ Research freak 💃🏼 GitHub: https://github.com/Buse5 Website: buse5.github.io