SOLID Prensipleri
Interface Segregation Principle
I — Arayüz Ayrımı Prensibi
Amaç: İstemciler kullanmadıkları metotlara bağımlı olmaya zorlanmamalıdır. Büyük, genel arayüzler yerine küçük ve amaca özel arayüzler tercih edilmelidir.
Basit Anlatım
Teknik terim yok — günlük hayattan bir benzetme
Bir restorana gittiniz ve sadece bir kahve içmek istiyorsunuz. Garson size 40 sayfalık bir menü getiriyor: kahvaltılar, çorbalar, ana yemekler, tatlılar, şarap listesi… Kahveyi bulmak için tüm menüyü karıştırıyorsunuz. Bunun yerine ayrı bir içecek menüsü olsaydı çok daha kolay olurdu.
İş ilanı örneği
Bir şirket "garson" arıyor ama ilanda şunlar yazıyor: "Yemek pişirebilmeli, muhasebe bilmeli, uçak kullanabilmeli, elektrik tesisatı döşeyebilmeli." Hiçbir garson bunların hepsini yapamaz. Yapamadığı işler için de "bunu yapamam" demek zorunda kalır.
Her işe bir uzmanlık tanımı olsaydı (garson, aşçı, muhasebeci), herkes sadece kendi yapabildiği işin tanımına uyardı.
Teknik Anlamı
Bir yazılımcının bakış açısıyla
ISP, "şişman" (fat) arayüzlerin parçalanmasını önerir. Bir sınıf bir arayüzü uyguladığında, arayüzdeki tüm metotları uygulamak zorundadır. Arayüz, sınıfın gerçekten yapamadığı işleri içeriyorsa sınıf bu metotları boş bırakır ya da istisna fırlatır.
Neden sorun?
- Boş veya
NotImplementedExceptionfırlatan metotlar LSP ihlaline yol açar. - Arayüzdeki ilgisiz bir metot değiştiğinde, o metodu hiç kullanmayan sınıflar da yeniden derlenmek ve dağıtılmak zorunda kalır.
- İstemci, arayüze baktığında hangi işlemlerin gerçekten çalıştığını bilemez.
İhlal belirtileri
throw new NotImplementedException()içeren implementasyonlar.IXxxManager,IXxxServicegibi onlarca metot içeren arayüzler.- Bir arayüzü kullanan istemcilerin her birinin metotların sadece küçük bir alt kümesini çağırması.
.NET'ten örnekler
IEnumerable<T>→IReadOnlyCollection<T>→ICollection<T>→IList<T>: Her seviye bir öncekine yeni yetenekler ekler. Sadece okuyacaksanızIEnumerable<T>veyaIReadOnlyList<T>istemek yeterlidir.IDisposable,IComparable<T>,IEquatable<T>: tek bir yetenek, tek bir metot.- CQRS'te okuma ve yazma arayüzlerinin ayrılması (
IOrderReader/IOrderWriter).
C# ile Uygulama
Adım adım, çalışan kod örneği
Senaryo: Bir ofiste farklı yeteneklere sahip cihazlar var: basit yazıcı, tarayıcı ve çok fonksiyonlu cihaz.
Adım 1 YANLIŞ Her şeyi içeren şişman arayüz
Basit yazıcı tarama ve faks yapamadığı halde bu metotları uygulamak zorunda. İstemci IMachine gördüğünde hangi metodun çalışacağını bilemez.
public interface IMachine
{
void Print(string doc);
void Scan(string doc);
void Fax(string doc, string number);
}
public class BasicPrinter : IMachine
{
public void Print(string doc) => Console.WriteLine($" Yazdırıldı: {doc}");
public void Scan(string doc) =>
throw new NotSupportedException("Bu yazıcı tarama yapamaz!");
public void Fax(string doc, string number) =>
throw new NotSupportedException("Bu yazıcı faks gönderemez!");
}Adım 2 DOĞRU Küçük, amaca özel arayüzler
Her arayüz tek bir yeteneği temsil ediyor. Sınıflar sadece gerçekten yapabildiklerini uyguluyor.
public interface IPrinter { void Print(string doc); }
public interface IScanner { string Scan(string doc); }
public interface IFax { void Fax(string doc, string number); }
public class BasicPrinter : IPrinter
{
public void Print(string doc) => Console.WriteLine($" [Yazıcı] {doc} yazdırıldı");
}
public class DocumentScanner : IScanner
{
public string Scan(string doc)
{
Console.WriteLine($" [Tarayıcı] {doc} tarandı");
return doc + ".pdf";
}
}
public class MultiFunctionDevice : IPrinter, IScanner, IFax
{
public void Print(string doc) => Console.WriteLine($" [ÇokFonk] {doc} yazdırıldı");
public string Scan(string doc)
{
Console.WriteLine($" [ÇokFonk] {doc} tarandı");
return doc + ".pdf";
}
public void Fax(string doc, string number) =>
Console.WriteLine($" [ÇokFonk] {doc} -> {number} numarasına fakslandı");
}Adım 3 DOĞRU İstemciler: sadece ihtiyaç duydukları yeteneği isterler
ReportService yalnızca yazdırmaya ihtiyaç duyuyor; IPrinter istemesi yeterli. Böylece hem basit yazıcıyla hem çok fonksiyonlu cihazla çalışır.
public class ReportService
{
private readonly IPrinter _printer;
public ReportService(IPrinter printer) => _printer = printer;
public void PrintMonthlyReport() => _printer.Print("Aylık Rapor");
}
public class ArchiveService
{
private readonly IScanner _scanner;
public ArchiveService(IScanner scanner) => _scanner = scanner;
public void Archive(string doc) =>
Console.WriteLine($" Arşive eklendi: {_scanner.Scan(doc)}");
}Adım 4 Kullanım
Tüm parçaları bir araya getiren istemci (client) kodu:
Console.WriteLine("Şişman arayüz:");
Kotu.IMachine machine = new Kotu.BasicPrinter();
machine.Print("Sözleşme");
try { machine.Scan("Sözleşme"); }
catch (NotSupportedException ex) { Console.WriteLine($" ÇÖKTÜ: {ex.Message}"); }
Console.WriteLine("\nAyrılmış arayüzler:");
var basic = new BasicPrinter();
var mfd = new MultiFunctionDevice();
new ReportService(basic).PrintMonthlyReport();
new ReportService(mfd).PrintMonthlyReport();
new ArchiveService(new DocumentScanner()).Archive("Fatura");
new ArchiveService(mfd).Archive("Dekont");
mfd.Fax("Teklif", "0212 555 00 00");
// new ArchiveService(basic); // DERLEME HATASI: BasicPrinter bir IScanner değilKonsol çıktısı
Şişman arayüz: Yazdırıldı: Sözleşme ÇÖKTÜ: Bu yazıcı tarama yapamaz! Ayrılmış arayüzler: [Yazıcı] Aylık Rapor yazdırıldı [ÇokFonk] Aylık Rapor yazdırıldı [Tarayıcı] Fatura tarandı Arşive eklendi: Fatura.pdf [ÇokFonk] Dekont tarandı Arşive eklendi: Dekont.pdf [ÇokFonk] Teklif -> 0212 555 00 00 numarasına fakslandı
Kodda dikkat edilmesi gerekenler
- Kötü tasarımda hata çalışma zamanında ortaya çıktı. İyi tasarımda basit yazıcıyı tarayıcı gibi kullanmaya çalışmak derleme zamanında engelleniyor.
MultiFunctionDevicebirden fazla küçük arayüzü uygulayarak tüm yeteneklerini sunuyor. Küçük arayüzler birleştirilebilir; büyük arayüzler ise bölünemez.