Yükleniyor...

C# ile SOLID Prensipleri

C# örnekleriyle SOLID prensiplerinin uygulanışı: daha esnek, sürdürülebilir ve test edilebilir kod tasarımları.

Yazılım geliştirmede SOLID prensipleri, Robert C. Martin tarafından tanımlanan ve nesne yönelimli programlamada (OOP) daha esnek, okunabilir ve sürdürülebilir sistemler geliştirmeyi amaçlayan 5 temel ilkedir. C# dilinde bu prensipler, sınıfların tasarımında yol gösterici kurallar olarak yaygın biçimde uygulanır.


1. Single Responsibility Principle (SRP) – Tek Sorumluluk İlkesi

Bir sınıfın sadece tek bir sorumluluğu olmalıdır. Yani değişmesi için yalnızca bir sebep olmalıdır. Eğer bir sınıf birden fazla işi yapıyorsa (ör. veri kaydetme + raporlama), bakımı zorlaşır ve hata yapma olasılığı artar.


// Yanlış: Hem sipariş kaydediyor hem fatura yazdırıyor
public class OrderManager
{
    public void SaveOrder(Order order) { /* kaydetme */ }
    public void PrintInvoice(Order order) { /* yazdırma */ }
}

// Doğru: Her sınıf tek sorumluluk taşıyor
public class OrderRepository
{
    public void Save(Order order) { /* kaydetme */ }
}

public class InvoicePrinter
{
    public void Print(Order order) { /* yazdırma */ }
}

2. Open/Closed Principle (OCP) – Açık/Kapalı İlkesi

Sınıflar genişletmeye açık, değişikliğe kapalı olmalıdır. Yani mevcut sınıfın kodunu değiştirmeden yeni davranışlar eklenebilmelidir. Bu, genellikle abstract class veya interface kullanılarak sağlanır.


// Yanlış: Yeni ödeme yöntemi eklemek için sınıf değiştirilmek zorunda
public class PaymentService
{
    public void Pay(string type)
    {
        if (type == "CreditCard") { /* ... */ }
        else if (type == "PayPal") { /* ... */ }
    }
}

// Doğru: Yeni yöntem eklemek için sadece yeni sınıf yazılır
public interface IPayment
{
    void Pay();
}

public class CreditCardPayment : IPayment
{
    public void Pay() { /* ... */ }
}

public class PayPalPayment : IPayment
{
    public void Pay() { /* ... */ }
}

3. Liskov Substitution Principle (LSP) – Liskov Yerine Geçme İlkesi

Türeyen sınıflar, temel sınıfın yerine kullanılabilmelidir. Yani bir alt sınıf, üst sınıfın beklendiği her yerde sorunsuz çalışmalıdır. Alt sınıf üst sınıfın sözleşmesini bozmamalıdır.


public abstract class Bird
{
    public abstract void Fly();
}

public class Sparrow : Bird
{
    public override void Fly() { Console.WriteLine("Serçe uçuyor"); }
}

// Yanlış: Penguen uçamaz, ama Fly() zorunlu kılındı
public class Penguin : Bird
{
    public override void Fly() { throw new NotImplementedException(); }
}

Burada Penguin sınıfı LSP’yi ihlal eder. Çünkü Bird soyut sınıfı “her kuş uçar” varsayımını dayatmıştır. Bunun yerine IFlyingBird ve INonFlyingBird gibi ayrı arayüzler tanımlamak gerekir.


4. Interface Segregation Principle (ISP) – Arayüz Ayrımı İlkesi

Bir sınıf, ihtiyaç duymadığı metotları içeren büyük arayüzleri uygulamak zorunda kalmamalıdır. Bunun yerine daha küçük, amaca yönelik arayüzler tercih edilmelidir.


// Yanlış: Tüm hayvanlar hem yürümek hem uçmak hem yüzmek zorunda bırakılıyor
public interface IAnimal
{
    void Walk();
    void Fly();
    void Swim();
}

// Doğru: Küçük arayüzlere ayrıldı
public interface IWalkable { void Walk(); }
public interface IFlyable { void Fly(); }
public interface ISwimmable { void Swim(); }

public class Dog : IWalkable, ISwimmable
{
    public void Walk() { /* ... */ }
    public void Swim() { /* ... */ }
}

5. Dependency Inversion Principle (DIP) – Bağımlılıkların Ters Çevrilmesi İlkesi

Yüksek seviye sınıflar, düşük seviye sınıflara doğrudan bağımlı olmamalıdır. Her ikisi de soyutlamalara (arayüz veya abstract class) bağımlı olmalıdır. Bu ilke, Dependency Injection uygulamalarında temel alınır.


// Yanlış: Service doğrudan ConsoleLogger'a bağımlı
public class Service
{
    private readonly ConsoleLogger _logger = new ConsoleLogger();
}

// Doğru: Soyutlamaya bağımlı
public interface ILogger
{
    void Log(string msg);
}

public class ConsoleLogger : ILogger
{
    public void Log(string msg) => Console.WriteLine(msg);
}

public class Service
{
    private readonly ILogger _logger;
    public Service(ILogger logger) { _logger = logger; }
}

Avantajları


TL;DR

  • SRP: Her sınıfın tek sorumluluğu olmalı.
  • OCP: Genişletmeye açık, değişikliğe kapalı.
  • LSP: Alt sınıflar üst sınıfların yerine geçebilmeli.
  • ISP: Büyük arayüzler yerine küçük ve odaklı arayüzler.
  • DIP: Somut sınıflara değil, soyutlamalara bağımlılık.

İlişkili Makaleler

C# Extension Metotlar

C#’ta extension metotları öğrenin. Mevcut sınıfları değiştirmeden yeni metotlar eklemeyi örneklerle keşfedin.