Skip to content

Bölüm 1 / 5

Tek Sorumluluk Prensibi

Bir sınıfın değişmesi için yalnızca tek bir nedeni olmalı. Peki bu “tek neden” pratikte ne anlama geliyor?

2 dakikalık okuma

SOLID'in ilk harfi S, Single Responsibility Principle, yani Tek Sorumluluk Prensibi. Robert C. Martin'in tanımı kısadır:

Bir modülün değişmesi için bir ve yalnızca bir nedeni olmalıdır.

Bu cümle genellikle “bir sınıf tek bir iş yapmalı” diye özetlenir. Ancak bu özet yanıltıcıdır. Prensibin özü iş değil, değişim nedenidir. Ve değişim nedenleri, çoğu zaman kodun kendisinden değil, ona ihtiyaç duyan insanlardan gelir.

Sorunu bir örnekle görelim

Bir SaaS ürününde abonelik faturalarını yöneten bir sınıf düşünelim:

InvoiceService.ts
class InvoiceService {
  calculateTotal(invoice: Invoice): number {
    const subtotal = invoice.items.reduce((sum, i) => sum + i.price * i.qty, 0);
    return subtotal * (1 + invoice.taxRate);
  }
 
  renderPdf(invoice: Invoice): Buffer {
    // Şablon, yazı tipi, logo yerleşimi...
  }
 
  save(invoice: Invoice): Promise<void> {
    return db.query("INSERT INTO invoices ...", [invoice]);
  }
 
  sendEmail(invoice: Invoice): Promise<void> {
    // SMTP ayarları, e-posta metni...
  }
}

İlk bakışta derli toplu görünüyor: her şey “fatura” ile ilgili. Ama bu sınıfı kimlerin değiştirmek isteyeceğine bakalım:

  • Finans ekibi vergi hesaplamasını değiştirmek isteyebilir.
  • Tasarım ekibi PDF şablonunu yenilemek isteyebilir.
  • Altyapı ekibi veritabanını değiştirmeye karar verebilir.
  • Pazarlama ekibi e-posta metnini güncellemek isteyebilir.

Dört farklı paydaş, dört farklı değişim nedeni. Her biri aynı dosyaya dokunuyor, aynı testleri kırma riski taşıyor.

Sorumlulukları ayırmak

Çözüm, her değişim nedenini kendi modülüne taşımaktır:

class InvoiceCalculator {
  total(invoice: Invoice): number {
    const subtotal = invoice.items.reduce((sum, i) => sum + i.price * i.qty, 0);
    return subtotal * (1 + invoice.taxRate);
  }
}
 
class InvoicePdfRenderer {
  render(invoice: Invoice): Buffer { /* ... */ }
}
 
class InvoiceRepository {
  save(invoice: Invoice): Promise<void> { /* ... */ }
}
 
class InvoiceMailer {
  send(invoice: Invoice, pdf: Buffer): Promise<void> { /* ... */ }
}

Artık vergi kuralı değiştiğinde yalnızca InvoiceCalculator değişir. PDF şablonu yenilendiğinde hesaplama testlerine dokunulmaz.

Peki bunları kim bir araya getirecek?

Parçaları birleştiren ince bir koordinasyon katmanı kullanırız. Bu katmanın görevi yalnızca akışı yönetmektir; iş kuralı içermez:

class IssueInvoice {
  constructor(
    private calculator: InvoiceCalculator,
    private renderer: InvoicePdfRenderer,
    private repo: InvoiceRepository,
    private mailer: InvoiceMailer,
  ) {}
 
  async execute(invoice: Invoice) {
    invoice.total = this.calculator.total(invoice);
    await this.repo.save(invoice);
    await this.mailer.send(invoice, this.renderer.render(invoice));
  }
}

Aşırıya kaçmamak

Tek sorumluluk, her fonksiyonu ayrı bir sınıfa bölmek demek değildir. Aynı nedenle ve aynı anda değişen kodlar bir arada durmalıdır. Ayırma kararını verirken şu soruyu sorun:

  1. Bu iki parça farklı kişilerin talebiyle mi değişir?
  2. Biri değiştiğinde diğerinin testleri de etkileniyor mu?
  3. Birini, diğerine dokunmadan yeniden kullanabilmek istiyor muyum?

Yanıtlar çoğunlukla “evet” ise ayırın. Değilse, erken soyutlama da bir teknik borçtur.

Özet

  • Prensip “tek iş” değil, tek değişim nedeni ile ilgilidir.
  • Değişim nedenlerini bulmanın en pratik yolu, kodun paydaşlarını düşünmektir.
  • Ayrılan parçaları ince bir koordinasyon katmanı bir araya getirir.

Bir sonraki bölümde, mevcut kodu değiştirmeden yeni davranış eklemeyi sağlayan Açık/Kapalı Prensibi'ni inceleyeceğiz.