SOLID Prensipleri
Dependency Inversion Principle
D — Bağımlılığın Tersine Çevrilmesi Prensibi
Amaç: Üst seviye modüller alt seviye modüllere bağımlı olmamalıdır; ikisi de soyutlamalara bağımlı olmalıdır. Soyutlamalar detaylara değil, detaylar soyutlamalara bağımlı olmalıdır.
Basit Anlatım
Teknik terim yok — günlük hayattan bir benzetme
Evinizdeki lambayı düşünün. Lamba duvara doğrudan lehimlenmiş olsaydı ne olurdu? Lambayı değiştirmek istediğinizde duvarı kırmanız, elektrik tesisatını sökmeniz gerekirdi. Başka bir odaya taşımak imkânsız olurdu.
Bunun yerine lamba bir fişe, duvar da bir prize sahiptir. Lamba duvarı tanımaz, duvar da lambayı tanımaz. İkisi de sadece "priz standardını" bilir. Böylece lambayı istediğiniz an değiştirebilir, duvara da lamba yerine şarj aleti takabilirsiniz.
Patron ve çalışan
Bir şirket müdürü "Bu raporu Ahmet hazırlasın" derse, Ahmet izne çıktığında iş durur. Bunun yerine "Bu raporu bir analist hazırlasın" derse, analist yetkinliğine sahip herkes işi yapabilir. Müdür bir kişiye değil, bir role bağımlıdır.
Teknik Anlamı
Bir yazılımcının bakış açısıyla
Geleneksel katmanlı mimaride üst seviye iş mantığı (ör. OrderService), alt seviye detaylara (ör. SqlRepository, SmtpClient) doğrudan bağımlıdır. Bu durumda veritabanını veya e-posta sağlayıcısını değiştirmek iş mantığını da değiştirmeyi gerektirir.
DIP bu bağımlılığı tersine çevirir: İş mantığı kendi ihtiyaç duyduğu arayüzü (IOrderRepository) tanımlar; alt seviye sınıflar bu arayüzü uygular. Artık ok yönü detaydan soyutlamaya doğrudur.
DIP, DI ve IoC farkı
- Dependency Inversion Principle (DIP): Bir tasarım prensibi. "Soyutlamalara bağımlı ol."
- Dependency Injection (DI): Bir teknik. Bağımlılıkları sınıfın içinde
newile oluşturmak yerine dışarıdan (genellikle constructor ile) vermek. - IoC Container: DI'yi otomatikleştiren araç. .NET'te
Microsoft.Extensions.DependencyInjection(builder.Services.AddScoped<IRepo, SqlRepo>()).
İhlal belirtileri
- İş mantığı sınıflarında
new SqlConnection(),new SmtpClient(),new HttpClient(). - Statik sınıflara doğrudan çağrılar:
DateTime.Now,File.ReadAllText. - Birim testi yazmak için gerçek bir veritabanı veya ağ bağlantısı gerekmesi.
Faydaları
- Test edilebilirlik: Gerçek bağımlılıklar yerine sahte (fake/mock) nesneler verilebilir.
- Esneklik: SQL Server'dan PostgreSQL'e, SMTP'den SMS'e geçmek iş mantığını etkilemez.
- Paralel geliştirme: Arayüz üzerinde anlaşıldıktan sonra ekipler bağımsız çalışabilir.
- Clean Architecture ve Onion Architecture'ın temelinde bu prensip vardır.
C# ile Uygulama
Adım adım, çalışan kod örneği
Senaryo: Bir sipariş servisi siparişi kaydediyor ve müşteriye bildirim gönderiyor.
Adım 1 YANLIŞ Üst seviye sınıf, somut detaylara kaynaklanmış
OrderService kendi bağımlılıklarını new ile oluşturuyor. SMS'e geçmek veya test için sahte veritabanı kullanmak imkânsız; OrderService'i değiştirmek zorundayız.
public class SqlServerDatabase
{
public void Insert(string order) => Console.WriteLine($" [SQL Server] {order} eklendi");
}
public class SmtpEmailClient
{
public void Send(string to, string msg) => Console.WriteLine($" [SMTP] {to}: {msg}");
}
public class OrderService
{
private readonly SqlServerDatabase _db = new(); // somut sınıfa bağımlılık
private readonly SmtpEmailClient _email = new(); // somut sınıfa bağımlılık
public void PlaceOrder(string customer, string product)
{
_db.Insert(product);
_email.Send(customer, $"{product} siparişiniz alındı.");
}
}Adım 2 DOĞRU Soyutlamalar: iş mantığının ihtiyaç tanımı
Arayüzler, onları kullanan üst seviye modülün ihtiyacına göre tasarlanır; veritabanının veya e-posta kütüphanesinin yeteneklerine göre değil.
public interface IOrderRepository
{
void Save(string customer, string product);
}
public interface INotifier
{
void Notify(string customer, string message);
}Adım 3 DOĞRU Üst seviye modül: sadece soyutlamaları bilir
Bağımlılıklar constructor üzerinden enjekte ediliyor (Dependency Injection).
public class OrderService
{
private readonly IOrderRepository _repository;
private readonly INotifier _notifier;
public OrderService(IOrderRepository repository, INotifier notifier)
{
_repository = repository;
_notifier = notifier;
}
public void PlaceOrder(string customer, string product)
{
_repository.Save(customer, product);
_notifier.Notify(customer, $"{product} siparişiniz alındı.");
}
}Adım 4 DOĞRU Alt seviye detaylar: soyutlamaları uygular
Yeni bir altyapı eklemek = yeni bir sınıf yazmak. OrderService değişmez.
public class SqlOrderRepository : IOrderRepository
{
public void Save(string c, string p) => Console.WriteLine($" [SQL Server] {p} kaydedildi");
}
public class PostgresOrderRepository : IOrderRepository
{
public void Save(string c, string p) => Console.WriteLine($" [PostgreSQL] {p} kaydedildi");
}
public class EmailNotifier : INotifier
{
public void Notify(string c, string m) => Console.WriteLine($" [E-posta] {c}: {m}");
}
public class SmsNotifier : INotifier
{
public void Notify(string c, string m) => Console.WriteLine($" [SMS] {c}: {m}");
}Adım 5 DOĞRU Test için sahte (fake) uygulamalar
Veritabanı veya ağ olmadan iş mantığını test edebiliyoruz.
public class InMemoryOrderRepository : IOrderRepository
{
public List<string> Saved { get; } = new();
public void Save(string c, string p) => Saved.Add(p);
}
public class FakeNotifier : INotifier
{
public int Count { get; private set; }
public void Notify(string c, string m) => Count++;
}Adım 6 Kullanım
Bağımlılıklar tek bir yerde (uygulamanın başlangıcında, "composition root") bir araya getiriliyor. ASP.NET Core'da bu işi DI container yapar.
Console.WriteLine("Kötü tasarım (değiştirilemez):");
new Kotu.OrderService().PlaceOrder("ali@mail.com", "Laptop");
Console.WriteLine("\nİyi tasarım - SQL Server + E-posta:");
new OrderService(new SqlOrderRepository(), new EmailNotifier())
.PlaceOrder("ali@mail.com", "Laptop");
Console.WriteLine("İyi tasarım - PostgreSQL + SMS (OrderService değişmedi):");
new OrderService(new PostgresOrderRepository(), new SmsNotifier())
.PlaceOrder("0555 111 22 33", "Telefon");
Console.WriteLine("\nBirim testi (veritabanı ve ağ olmadan):");
var repo = new InMemoryOrderRepository();
var notifier = new FakeNotifier();
new OrderService(repo, notifier).PlaceOrder("test", "Kalem");
bool passed = repo.Saved.Contains("Kalem") && notifier.Count == 1;
Console.WriteLine($" Test sonucu: {(passed ? "BAŞARILI" : "BAŞARISIZ")}");
// ASP.NET Core'da aynı bağlama işini DI container yapar:
// builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
// builder.Services.AddScoped<INotifier, EmailNotifier>();
// builder.Services.AddScoped<OrderService>();Konsol çıktısı
Kötü tasarım (değiştirilemez): [SQL Server] Laptop eklendi [SMTP] ali@mail.com: Laptop siparişiniz alındı. İyi tasarım - SQL Server + E-posta: [SQL Server] Laptop kaydedildi [E-posta] ali@mail.com: Laptop siparişiniz alındı. İyi tasarım - PostgreSQL + SMS (OrderService değişmedi): [PostgreSQL] Telefon kaydedildi [SMS] 0555 111 22 33: Telefon siparişiniz alındı. Birim testi (veritabanı ve ağ olmadan): Test sonucu: BAŞARILI
Kodda dikkat edilmesi gerekenler
- Aynı
OrderServicesınıfı, hiç değişmeden üç farklı konfigürasyonla çalıştı. - Testte gerçek veritabanı veya e-posta sunucusu kullanılmadı; bu yalnızca bağımlılıklar soyutlamalar üzerinden verildiği için mümkün oldu.
- Diğer SOLID prensipleriyle ilişkisi: Arayüzler küçük tutuldu (ISP), her sahte sınıf gerçeğinin yerine geçebiliyor (LSP) ve yeni altyapı eklemek mevcut kodu değiştirmiyor (OCP).