SOLID prensipleri yazılım tasarımında kullanılan beş temel prensiptir. Bu prensipler, yazılımın daha esnek, sürdürülebilir ve kolayca genişletilebilir olmasını sağlamak için kullanılır.
İşte SOLID prensiplerinin her biri:
Single Responsibility Principle (SRP) - Tek Sorumluluk Prensibi: Bir sınıfın veya modülün yalnızca tek bir sorumluluğu olmalıdır. Bu prensibe göre, bir sınıfın yalnızca bir işlevi yerine getirmesi gerekir. Bu, bir sınıfın kodunun daha okunaklı ve bakımı daha kolay olmasını sağlar.
Open/Closed Principle (OCP) - Açık/Kapalı Prensibi: Bir sınıfın veya modülün değiştirilebilir olması, ancak mevcut işlevselliğinin etkilenmemesi gerekir. Bu prensip, kodun genişletilebilirliğini artırır ve değişiklik yapmanın diğer sınıflara etkisini minimize eder.
Liskov Substitution Principle (LSP) - Liskov Yerine Geçme Prensibi: Alt sınıflar, üst sınıfların yerini alabilir. Yani bir üst sınıfın tüm özelliklerine sahip olmalıdır. Bu prensip, kalıtım ve polimorfizmin doğru kullanımını teşvik eder ve kodun daha esnek olmasını sağlar.
Interface Segregation Principle (ISP) - Arayüz Ayırma Prensibi: Bir sınıf, kendi ihtiyaçlarını karşılamak için gereksiz arayüzleri uygulamamalıdır. Bu prensip, arayüzlerin daha küçük, daha spesifik ve daha kullanışlı hale getirilmesini sağlar.
Dependency Inversion Principle (DIP) - Bağımlılık Tersine Çevirme Prensibi: Yüksek seviyeli modüller, düşük seviyeli modüllere bağımlı olmamalıdır. Bunun yerine, her iki modül de soyutlamalara bağımlı olmalıdır. Bu prensip, kodun daha esnek olmasını sağlar ve değişikliklerin diğer modülleri etkilemesini azaltır.
Bu prensipler birbirleriyle bağlantılı ve birbirini tamamlayan prensiplerdir. SOLID prensiplerinin uygulanması, daha temiz, okunaklı ve sürdürülebilir bir kod yazmanıza yardımcı olacaktır.
Single Responsibility Principle (SRP), yazılım tasarımı için önemli bir prensiptir. Bu prensip, her sınıfın ya da modülün, yalnızca tek bir sorumluluğu olması gerektiğini savunur. Bu sayede sınıfların ve modüllerin daha anlaşılır, bakımı kolay ve yeniden kullanılabilir hale gelmesi hedeflenir.
Örnek olarak, bir müşteri bilgisi tutan bir sınıfı ele alalım. Bu sınıf, müşteri bilgilerini saklar, müşteri siparişlerini takip eder ve müşteriye özel indirimler uygular. Ancak bu durumda, sınıfın üstlendiği sorumluluklar çok fazla olduğu için SRP'ye aykırıdır.
Bunun yerine, müşteri bilgilerini saklayan bir sınıf ve müşteri siparişlerini takip eden başka bir sınıf oluşturabiliriz. Bu sayede her sınıfın yalnızca bir sorumluluğu olur ve bu sınıflar daha anlaşılır, bakımı kolay ve yeniden kullanılabilir hale gelir.
Örneğin:
public class MusteriBilgisi
{
public string Isim { get; set; }
public string Soyisim { get; set; }
public string Telefon { get; set; }
public string Email { get; set; }
}
public class MusteriSiparisleri
{
private List<string> siparisler = new List<string>();
public MusteriBilgisi Musteri { get; set; }
public void SiparisEkle(string siparis)
{
siparisler.Add(siparis);
}
public void SiparisSil(string siparis)
{
siparisler.Remove(siparis);
}
public List<string> SiparisleriListele()
{
return siparisler;
}
}
Yukarıdaki örnekte, MusteriBilgisi
sınıfı yalnızca müşteri bilgilerini tutarken, MusteriSiparisleri
sınıfı yalnızca müşteri siparişlerini takip eder. Her sınıf yalnızca bir sorumluluğu üstlenir ve böylece SRP prensibine uygun bir şekilde tasarlanmış olur.
Open/Closed Principle (OCP), yazılım tasarımında önemli bir prensiptir. Bu prensip, yazılım bileşenlerinin (sınıflar, modüller, fonksiyonlar vb.) değişime kapalı (closed) olmalı, ancak eklentilere açık (open) olması gerektiğini savunur. Yani var olan kodlar değiştirilmeden, yeni özellikler eklemek mümkün olmalıdır.
OCP prensibini bir örnek ile açıklamak gerekirse, bir uygulamada bir ödeme sistemi sınıfı düşünelim. Bu sınıf, bir siparişin ödeme işlemini gerçekleştirir. Ancak, ödeme işlemlerinde kullanılan ödeme yöntemleri değişebilir. Örneğin, kredi kartı yerine, PayPal ya da banka havalesi kullanılabilir.
Eğer bu durumda ödeme sistemimizi her yeni ödeme yöntemi için değiştirmeye kalkarsak, OCP prensibine aykırı oluruz. Bunun yerine, ödeme yöntemleri için ayrı birer sınıf oluşturup, ana ödeme sistemi sınıfının bu sınıflarla etkileşime geçmesi sağlanabilir. Böylece ana sınıf değişime kapalı kalırken, yeni ödeme yöntemleri eklenerek uygulama özellikleri genişletilebilir.
Aşağıda C# ile bir OCP örneği verilmiştir:
public abstract class OdemeYontemi
{
public abstract void OdemeYap(double tutar);
}
public class KrediKarti : OdemeYontemi
{
public override void OdemeYap(double tutar)
{
// Kredi kartı ödeme işlemleri burada gerçekleştirilir
}
}
public class PayPal : OdemeYontemi
{
public override void OdemeYap(double tutar)
{
// PayPal ödeme işlemleri burada gerçekleştirilir
}
}
public class BankaHavalesi : OdemeYontemi
{
public override void OdemeYap(double tutar)
{
// Banka havalesi ödeme işlemleri burada gerçekleştirilir
}
}
public class OdemeSistemi
{
private OdemeYontemi odemeYontemi;
public OdemeSistemi(OdemeYontemi odemeYontemi)
{
this.odemeYontemi = odemeYontemi;
}
public void OdemeYap(double tutar)
{
odemeYontemi.OdemeYap(tutar);
}
}
Yukarıdaki örnekte, IOdemeYontemi
adında bir arayüz tanımladık ve KrediKarti
ve Paypal
adında iki sınıf oluşturduk. Her iki sınıf da IOdemeYontemi
arayüzünü uygular ve kendi ödeme yöntemlerini gerçekleştirir. Daha sonra, Sepet
sınıfında OdemeYap()
adında bir metot ekledik ve bu metoda bir IOdemeYontemi
örneği vererek ödeme yapma işlemini gerçekleştirdik. Bu sayede, herhangi bir ödeme yöntemi eklemek istediğimizde, IOdemeYontemi
arayüzünü uygulayan yeni bir sınıf oluşturarak Sepet
sınıfındaki OdemeYap()
metodu ile kullanabiliriz. Bu şekilde, Sepet
sınıfındaki kodu değiştirmeden, yeni bir ödeme yöntemi ekleyebiliriz.
Liskov Substitution Principle (LSP), bir yazılım nesnesinin, onun alt sınıflarından herhangi biriyle değiştirildiğinde programın doğru şekilde çalışması gerektiği prensibidir. Bu prensip, yazılım tasarımı açısından önemlidir ve Open/Closed Prensibi'nin (OCP) bir uzantısıdır.
LSP, bir sınıfın türetilmiş sınıflar tarafından genişletilebilir olması gerektiğini belirtir. Yani, bir sınıfın herhangi bir alt sınıfı, ana sınıfın tüm davranışlarını yerine getirmelidir. Bu, ana sınıfın yerine alt sınıfın kullanılması durumunda hiçbir sorun olmamasını sağlar.
LSP, programlama dilindeki polimorfizmin kullanımına özellikle önem verir. Bu prensibe uygun bir şekilde tasarlanmış bir sistem, polimorfizm kullanılarak yazılmış kodun daha sürdürülebilir, değiştirilebilir ve ölçeklenebilir olmasını sağlar.
Özetle, Liskov Substitution Prensibi, yazılım tasarımında, alt sınıfların ana sınıflarının yerine kullanılabileceği bir prensiptir. Bu, kodun daha güvenilir ve esnek olmasını sağlar.
using System;
public class Animal
{
public virtual void MakeSound()
{
Console.WriteLine("The animal makes a sound");
}
}
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("The dog barks");
}
}
public class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine("The cat meows");
}
}
public class Program
{
static void Main(string[] args)
{
Animal animal1 = new Animal();
Animal animal2 = new Dog();
Animal animal3 = new Cat();
animal1.MakeSound(); // Outputs "The animal makes a sound"
animal2.MakeSound(); // Outputs "The dog barks"
animal3.MakeSound(); // Outputs "The cat meows"
}
}
Bu örnekte, Animal
sınıfı Dog
ve Cat
sınıfları tarafından genişletilir ve her alt sınıf, MakeSound()
metodunu uyarlar. Program
sınıfında, Animal
tipinde üç nesne oluşturulur ve her biri MakeSound()
metodunu çağırır. Bu örnek, Liskov Substitution Prensibi'nin uygulandığını gösterir, çünkü her alt sınıf ana sınıfın yerine kullanılabilir ve programın doğru şekilde çalışması sağlanır.
Interface Segregation Principle (ISP), yazılım tasarımındaki bir prensiptir ve bir arayüzün bir sınıfın gereksinimlerinin sadece bir kısmını karşılaması durumunda, arayüzün(interface) bölünmesini veya parçalanmasını önerir. ISP, SOLID prensipleri arasında yer alır ve kodun daha okunaklı, bakımı kolay ve genişletilebilir olmasını sağlar.
ISP, bir sınıfın ihtiyaç duymadığı arayüzleri uygulamamasını önerir. Bu şekilde, bir sınıf, gereksiz fonksiyonlara veya yöntemlere sahip arayüzlerin yükünü taşımaktan kaçınabilir. Bunun yerine, yalnızca sınıfın ihtiyacı olan işlevselliği içeren arayüzler (interface) uygulanmalıdır. Bu, kodun daha modüler ve daha az bağımlı hale gelmesine olanak tanır.
ISP, sınıfların birbirinden bağımsız ve yeniden kullanılabilir olmasını sağlar. Arayüzler (interface), kodun yeniden kullanılmasını kolaylaştırır ve yazılımın daha sürdürülebilir hale gelmesini sağlar. ISP, uygulama boyunca gereksinimlerin değişmesi durumunda, yazılımın yeniden yapılandırılmasını kolaylaştırır.
Özetle, Interface Segregation Prensibi, bir sınıfın yalnızca ihtiyacı olan işlevselliği içeren arayüzlerin uygulanmasını önerir. Bu, kodun daha okunaklı, bakımı kolay ve genişletilebilir olmasını sağlar.
Aşağıdaki C# programlama dilindeki örnek, Interface Segregation Prensibi'ni uygulayan bir arayüz kullanımını göstermektedir:
using System;
public interface IAnimal
{
void Eat();
void Sleep();
}
public interface IPet
{
void Play();
}
public class Dog : IAnimal, IPet
{
public void Eat()
{
Console.WriteLine("The dog eats its food");
}
public void Sleep()
{
Console.WriteLine("The dog sleeps in its bed");
}
public void Play()
{
Console.WriteLine("The dog plays with its owner");
}
}
public class Cat : IAnimal, IPet
{
public void Eat()
{
Console.WriteLine("The cat eats its food");
}
public void Sleep()
{
Console.WriteLine("The cat sleeps in its bed");
}
public void Play()
{
Console.WriteLine("The cat plays with its owner");
}
}
public class Program
{
static void Main(string[] args)
{
Dog dog = new Dog();
Cat cat = new Cat();
dog.Eat(); // Outputs "The dog eats its food"
cat.Sleep(); // Outputs "The cat sleeps in its bed"
dog.Play(); // Outputs "The dog plays with its owner"
cat.Play(); // Outputs "The cat plays with its owner"
}
}
Bu örnekte, IAnimal
arayüzü Eat()
ve Sleep()
yöntemlerini tanımlar ve IPet
arayüzü Play()
yöntemini tanımlar. Dog
ve Cat
sınıfları her iki arayüzü de uygular ve her yöntemi uyarlar. Program
sınıfında, Dog
ve Cat
nesneleri oluşturulur ve her yöntem çağrılır.
Bu örnek, Interface Segregation Prensibi'nin uygulandığını gösterir, çünkü Dog
ve Cat
sınıfları yalnızca ihtiyaç duydukları yöntemleri içeren arayüzleri uygularlar. Bu, kodun daha modüler ve daha az bağımlı hale gelmesine olanak tanır.
Dependency Inversion Principle (DIP) yazılım tasarımı alanındaki SOLID prensiplerinden biridir ve kodun bağımlılıklarını azaltmaya ve daha esnek hale getirmeye yardımcı olur. DIP'ye göre, yüksek seviyeli modüller, düşük seviyedeki modüllere doğrudan bağımlı olmamalıdır. Bunun yerine, her iki modül de soyutlamalara (abstractions) bağımlı olmalıdır.
DIP prensibinin uygulanması, soyutlamaların (abstractions) ve somut uygulamaların (implementations) ayrılmasını gerektirir. Yani, yüksek seviyeli modüller, düşük seviyedeki modüllere doğrudan bağlanmak yerine, soyutlamalar kullanarak düşük seviyedeki modüllere erişir. Bu, daha düşük seviyedeki modüllerin değiştirilmesi durumunda bile yüksek seviyedeki modüllerin etkilenmeyeceği anlamına gelir.
DIP, yazılımın yeniden kullanılabilirliğini arttırır ve kodun daha az bağımlı ve daha esnek olmasını sağlar. Bu prensip, özellikle büyük yazılım projelerinde, kodun yönetilebilirliğini ve bakımını kolaylaştırmak için önemlidir.
Bir örnek vermek gerekirse, bir müşteri veritabanındaki bilgileri işleyen bir yazılımımız var diyelim. Veritabanı bağlantısını yöneten bir modülümüz de var. DIP prensibini kullanarak, müşteri verilerini işleyen modülümüz veritabanına doğrudan bağlanmak yerine, bir ara yüz (interface) kullanarak veritabanı bağlantısına erişebilir. Bu, veritabanı bağlantısı değiştirildiğinde müşteri verilerini işleyen modülün etkilenmeyeceği anlamına gelir. Bu, modüller arasındaki bağımlılığı azaltır ve kodun daha esnek hale gelmesini sağlar.
public interface IDataProvider
{
void SaveData(string data);
}
public class DatabaseProvider : IDataProvider
{
public void SaveData(string data)
{
// Veritabanına kaydetme işlemleri
}
}
public class TextFileProvider : IDataProvider
{
public void SaveData(string data)
{
// Metin dosyasına kaydetme işlemleri
}
}
public class DataProcessor
{
private readonly IDataProvider _dataProvider;
public DataProcessor(IDataProvider dataProvider)
{
_dataProvider = dataProvider;
}
public void ProcessData(string data)
{
// Veri işleme işlemleri
_dataProvider.SaveData(data);
}
}
Bu kodda, IDataProvider
arayüzü veri kaydetme işlemlerini sağlayan bir SaveData
yöntemi tanımlar. Ardından, DatabaseProvider
ve TextFileProvider
sınıfları, bu arayüzü uygular ve her biri kendi kaydetme işlemlerini gerçekleştirir.
DataProcessor
sınıfı ise, IDataProvider
arayüzünü kullanarak verileri işler ve SaveData
yöntemini kullanarak verileri kaydeder. Ancak DataProcessor
sınıfı, hangi veri sağlayıcısının kullanılacağına karar vermez. Bunun yerine, veri sağlayıcısı, sınıfın yapılandırıcısına IDataProvider
arayüzü üzerinden verilir. Böylece, DataProcessor
sınıfı, veri sağlayıcısının değiştirilmesi durumunda etkilenmeyecek şekilde esnek hale gelir.