SOLID Prensipleri

Single Responsibility Principle

S — Tek Sorumluluk Prensibi

Amaç: Bir sınıfın değişmek için yalnızca tek bir nedeni olmalıdır. Yani her sınıf tek bir işten (tek bir paydaşın ihtiyacından) sorumlu olmalıdır.

1

Basit Anlatım

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

Bir restoranı düşünün. Aşçı yemek yapar, garson sipariş alır ve servis eder, kasiyer hesabı keser. Her biri kendi işinde uzmandır.

Şimdi bütün bu işleri tek bir kişinin yaptığını düşünün: hem yemek pişiriyor, hem masalara koşuyor, hem kasada duruyor. Kasada sıra birikince yemek yanar. Yemek yanınca müşteriler bekler. Bir işteki sorun diğer bütün işleri etkiler. Ve bu kişi hastalandığında restoran tamamen durur.

İsviçre çakısı benzetmesi

İsviçre çakısı her şeyi yapar: bıçak, makas, tornavida, şişe açacağı. Kamp için pratiktir. Ama bir marangoz atölyesinde kimse çakıyla çalışmaz; her iş için o işe özel alet kullanılır. Makas bozulduğunda bütün çakıyı tamire vermek zorunda kalmak istemezsiniz.

Tek Sorumluluk Prensibi şunu der: "Herkes kendi işini yapsın. Bir şey değişecekse, sadece o işten sorumlu olan yer değişsin."
2

Teknik Anlamı

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

Robert C. Martin (Uncle Bob) bu prensibi şöyle tanımlar: "Bir modülün değişmek için bir ve yalnızca bir nedeni olmalıdır." Daha sonra bunu netleştirmiştir: "Bir modül, yalnızca bir aktöre (paydaşa) karşı sorumlu olmalıdır."

"Tek sorumluluk" ne demek?

Yaygın yanlış anlama: "Her sınıfın tek bir metodu olmalı." Doğrusu: Sınıftaki her şey aynı nedenle değişmelidir. Örneğin bir fatura sınıfında:

Üç farklı paydaş = değişmek için üç farklı neden = üç farklı sınıf olmalı.

İhlal belirtileri (code smells)

Faydaları

Aşırıya kaçmamak gerekir: her satırı ayrı sınıfa bölmek de okunabilirliği bozar. Ölçüt "değişim nedeni" olmalıdır, satır sayısı değil.
3

C# ile Uygulama

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

Senaryo: Bir fatura işlemi. Önce prensibi ihlal eden tasarımı, ardından düzeltilmiş halini göreceğiz. (Kötü örnek Kotu namespace'inde duruyor.)

Adım 1 YANLIŞ Her şeyi yapan sınıf

Hesaplama, kalıcılık (veritabanı), bildirim ve biçimlendirme aynı sınıfta. Vergi oranı değişse de, veritabanı değişse de, e-posta şablonu değişse de bu sınıfa dokunmak zorundayız.

public class InvoiceService
{
    public void Process(string customer, decimal amount)
    {
        // 1) İş kuralı: vergi hesaplama
        decimal total = amount * 1.20m;

        // 2) Kalıcılık: veritabanına kaydetme
        Console.WriteLine($"  INSERT INTO Invoices VALUES ('{customer}', {total})");

        // 3) Bildirim: e-posta gönderme (SMTP ayarları dahil!)
        Console.WriteLine($"  SMTP smtp.firma.com:587 -> {customer}: Faturanız {total} TL");

        // 4) Sunum: rapor biçimlendirme
        Console.WriteLine($"  === FATURA === {customer} | {total:N2} TL");
    }
}

Adım 2 DOĞRU Her sorumluluk kendi sınıfında

Her sınıfın değişmek için tek bir nedeni var. Vergi kuralı değişirse sadece TaxCalculator, veritabanı değişirse sadece InvoiceRepository değişir.

public record Invoice(string Customer, decimal Amount, decimal Total);

public class TaxCalculator                     // Değişim nedeni: vergi mevzuatı
{
    private const decimal VatRate = 0.20m;
    public decimal AddVat(decimal amount) => amount * (1 + VatRate);
}

public class InvoiceRepository                 // Değişim nedeni: veri saklama teknolojisi
{
    public void Save(Invoice invoice) =>
        Console.WriteLine($"  [DB] Fatura kaydedildi: {invoice.Customer}, {invoice.Total}");
}

public class InvoiceMailer                     // Değişim nedeni: iletişim kanalı/şablon
{
    public void Send(Invoice invoice) =>
        Console.WriteLine($"  [Mail] {invoice.Customer}: Faturanız {invoice.Total:N2} TL");
}

public class InvoicePrinter                    // Değişim nedeni: görsel format
{
    public string Format(Invoice invoice) =>
        $"=== FATURA === {invoice.Customer} | {invoice.Total:N2} TL";
}

Adım 3 DOĞRU Koordinasyon: sadece akışı yöneten sınıf

InvoiceProcessor hiçbir detayı bilmez; yalnızca adımları sırayla çağırır. Onun değişim nedeni de tektir: iş akışının kendisi.

public class InvoiceProcessor
{
    private readonly TaxCalculator _tax = new();
    private readonly InvoiceRepository _repository = new();
    private readonly InvoiceMailer _mailer = new();
    private readonly InvoicePrinter _printer = new();

    public void Process(string customer, decimal amount)
    {
        var invoice = new Invoice(customer, amount, _tax.AddVat(amount));
        _repository.Save(invoice);
        _mailer.Send(invoice);
        Console.WriteLine("  " + _printer.Format(invoice));
    }
}

Adım 4 Kullanım

Dışarıdan bakıldığında iki versiyon aynı işi yapıyor; fark bakımda ortaya çıkar:

Console.WriteLine("Kötü tasarım:");
new Kotu.InvoiceService().Process("ABC Ltd.", 1000m);

Console.WriteLine("İyi tasarım:");
new InvoiceProcessor().Process("ABC Ltd.", 1000m);

// Sorumluluklar ayrı olduğu için tek başına test edilebilir:
var tax = new TaxCalculator();
Console.WriteLine($"Vergi testi: 1000 -> {tax.AddVat(1000m)} (beklenen 1200,00)");

Konsol çıktısı

Kötü tasarım:
  INSERT INTO Invoices VALUES ('ABC Ltd.', 1200,00)
  SMTP smtp.firma.com:587 -> ABC Ltd.: Faturanız 1200,00 TL
  === FATURA === ABC Ltd. | 1.200,00 TL
İyi tasarım:
  [DB] Fatura kaydedildi: ABC Ltd., 1200,00
  [Mail] ABC Ltd.: Faturanız 1.200,00 TL
  === FATURA === ABC Ltd. | 1.200,00 TL
Vergi testi: 1000 -> 1200,00 (beklenen 1200,00)

Kodda dikkat edilmesi gerekenler

Özetle: Bir sınıf tek bir şeyden sorumlu olmalı ve değişmek için tek bir nedeni olmalıdır. Farklı nedenlerle değişen kodları ayırın.
← Ana sayfaya dön