SOLID Prensipleri

Open/Closed Principle

O — Açık/Kapalı Prensibi

Amaç: Yazılım varlıkları (sınıflar, modüller, fonksiyonlar) genişlemeye açık, değişikliğe kapalı olmalıdır.

1

Basit Anlatım

Teknik terim yok — günlük hayattan bir benzetme

Bilgisayarınızın USB girişlerini düşünün. Fare, klavye, harici disk, kamera, mikrofon… Yeni bir cihaz aldığınızda bilgisayarın kasasını açıp anakartı yeniden lehimlemezsiniz. Cihazı USB'ye takarsınız, çalışır.

Bilgisayar değişikliğe kapalıdır (içini açıp kurcalamıyorsunuz) ama genişlemeye açıktır (istediğiniz kadar yeni cihaz ekleyebiliyorsunuz). Bunu mümkün kılan, herkesin uyduğu ortak bir standart: USB.

Priz örneği

Evinizdeki duvar prizine buzdolabı, ütü, şarj aleti takabilirsiniz. Yeni bir cihaz aldığınızda elektrikçi çağırıp duvarı kırmazsınız. Priz bir kez yapılır; ona uyan her şey çalışır.

Açık/Kapalı Prensibi şunu der: "Yeni bir özellik eklemek için mevcut, çalışan kodu kurcalama; sisteme yeni bir parça tak."
2

Teknik Anlamı

Bir yazılımcının bakış açısıyla

Bertrand Meyer tarafından ortaya konan ve Robert C. Martin tarafından popülerleştirilen bu prensibe göre, yeni bir davranış eklemek için mevcut kodu değiştirmek yerine yeni kod eklemek gerekir. Bu genellikle soyutlama (interface / abstract class) ve polimorfizm ile sağlanır.

Neden önemli?

İhlal belirtileri

Nasıl uygulanır?

Her şeyi baştan soyutlamaya çalışmayın (YAGNI). Bir noktada değişikliğin tekrar ettiğini gördüğünüzde o noktayı genişlemeye açık hale getirin.
3

C# ile Uygulama

Adım adım, çalışan kod örneği

Senaryo: Bir e-ticaret sitesinde müşteri tipine göre indirim hesaplama. Pazarlama ekibi sık sık yeni indirim türleri istiyor.

Adım 1 YANLIŞ Her yeni indirim için sınıfı değiştirmek

"Öğrenci indirimi" eklemek istediğimizde bu metodu açıp yeni bir case yazmak zorundayız. Her değişiklik, mevcut indirimleri bozma riski taşır.

public class DiscountCalculator
{
    public decimal Calculate(string customerType, decimal amount)
    {
        switch (customerType)
        {
            case "Normal":  return amount;
            case "Gold":    return amount * 0.90m;
            case "Premium": return amount * 0.80m;
            // Yeni tür = bu sınıfı tekrar açıp değiştirmek!
            default: throw new ArgumentException("Bilinmeyen müşteri tipi");
        }
    }
}

Adım 2 DOĞRU Soyutlama: genişleme noktası

Değişen kısım (indirim kuralı) bir arayüzün arkasına alındı.

public interface IDiscountPolicy
{
    string Name { get; }
    bool AppliesTo(Customer customer);
    decimal Apply(decimal amount);
}

public record Customer(string Name, string Type, bool IsStudent = false, int Years = 0);

Adım 3 DOĞRU Mevcut politikalar

public class GoldDiscount : IDiscountPolicy
{
    public string Name => "Gold %10";
    public bool AppliesTo(Customer c) => c.Type == "Gold";
    public decimal Apply(decimal amount) => amount * 0.90m;
}

public class PremiumDiscount : IDiscountPolicy
{
    public string Name => "Premium %20";
    public bool AppliesTo(Customer c) => c.Type == "Premium";
    public decimal Apply(decimal amount) => amount * 0.80m;
}

Adım 4 DOĞRU Değişikliğe kapalı hesaplayıcı

Bu sınıf bir daha değişmeyecek. Politikalar ona dışarıdan verilir; en avantajlı olanı seçer.

public class PriceCalculator
{
    private readonly IEnumerable<IDiscountPolicy> _policies;

    public PriceCalculator(IEnumerable<IDiscountPolicy> policies) => _policies = policies;

    public void PrintPrice(Customer customer, decimal amount)
    {
        var best = _policies
            .Where(p => p.AppliesTo(customer))
            .OrderBy(p => p.Apply(amount))
            .FirstOrDefault();

        decimal final = best?.Apply(amount) ?? amount;
        Console.WriteLine($"  {customer.Name,-6} ({best?.Name ?? "indirim yok",-14}) " +
                          $"-> {final,8:N2} TL");
    }
}

Adım 5 DOĞRU Yeni gereksinim: SADECE yeni sınıf ekliyoruz

Pazarlama "öğrenci" ve "sadakat" indirimi istedi. Mevcut hiçbir dosyaya dokunmadan iki yeni sınıf ekledik.

public class StudentDiscount : IDiscountPolicy
{
    public string Name => "Öğrenci %25";
    public bool AppliesTo(Customer c) => c.IsStudent;
    public decimal Apply(decimal amount) => amount * 0.75m;
}

public class LoyaltyDiscount : IDiscountPolicy
{
    public string Name => "Sadakat -150TL";
    public bool AppliesTo(Customer c) => c.Years >= 5;
    public decimal Apply(decimal amount) => Math.Max(0, amount - 150m);
}

Adım 6 Kullanım

Tüm parçaları bir araya getiren istemci (client) kodu:

var policies = new List<IDiscountPolicy>
{
    new GoldDiscount(), new PremiumDiscount(),
    new StudentDiscount(), new LoyaltyDiscount(),   // sonradan eklenenler
};
var calculator = new PriceCalculator(policies);

var customers = new[]
{
    new Customer("Ali", "Normal"),
    new Customer("Ayşe", "Gold"),
    new Customer("Can", "Normal", IsStudent: true),
    new Customer("Derya", "Gold", Years: 7),
};
foreach (var c in customers)
    calculator.PrintPrice(c, 1000m);

var oldWay = new Kotu.DiscountCalculator().Calculate("Gold", 1000m);
Console.WriteLine($"Eski yöntem (Gold): {oldWay:N2} TL");

Konsol çıktısı

  Ali    (indirim yok   ) -> 1.000,00 TL
  Ayşe   (Gold %10      ) ->   900,00 TL
  Can    (Öğrenci %25   ) ->   750,00 TL
  Derya  (Sadakat -150TL) ->   850,00 TL
Eski yöntem (Gold): 900,00 TL

Kodda dikkat edilmesi gerekenler

Özetle: Yeni davranış eklemek için çalışan kodu değiştirmeyin; soyutlamalar sayesinde yeni kod ekleyerek sistemi genişletin.
← Ana sayfaya dön