SOLID prensipleri, Nesne Yönelimli Programlama (OOP) kullanarak daha esnek, sürdürülebilir ve anlaşılır yazılımlar oluşturmak için bize yol gösteren beş temel ilkeyi içerir. Bu prensiplerden biri olan ve SOLID kısaltmasındaki ‘I’ harfini temsil eden Arayüz Ayrım Prensibi (Interface Segregation Principle — ISP), sınıfların ve onlara bağımlı olan istemcilerin nasıl etkileşim kurması gerektiği üzerine odaklanır. Temelde, büyük ve her şeyi kapsayan “şişman” arayüzler yerine, daha küçük, daha spesifik ve amaca yönelik arayüzler tasarlamayı önerir.

ISP’nin ana fikri, bir sınıfın (veya istemcinin), ihtiyaç duymadığı veya kullanmadığı metotları içeren bir arayüze bağımlı olmaya zorlanmaması gerektiğidir. Eğer bir arayüz çok fazla farklı sorumluluğu veya yeteneği bir araya getiriyorsa, bu arayüzü implemente eden sınıflar kullanmayacakları metotları boşuna implemente etmek zorunda kalabilir ve bu arayüze bağımlı olan istemciler, aslında kullanmadıkları metotlardaki değişikliklerden bile etkilenebilirler.

Bu rehberde, Arayüz Ayrım Prensibi’nin ne anlama geldiğini, “şişman arayüzler” (fat interfaces) problemini, bu prensibin neden önemli olduğunu ve yazılım tasarımına sağladığı faydaları detaylı bir şekilde ele alacağız. Python’da, doğrudan interface anahtar kelimesi olmasa da, ISP'nin ruhuna uygun tasarımların nasıl yapılabileceğini (özellikle Soyut Temel Sınıflar - ABCs ve Protokoller kullanarak) örneklerle açıklayacağız ve prensibin ihlal edildiği durumları ve çözümlerini inceleyeceğiz.

Bölüm 1: Arayüz Ayrım Prensibi (ISP) Nedir?
Tanım
İstemciler (clients), kullanmadıkları metotlara sahip arayüzlere bağımlı olmaya zorlanmamalıdır.
(Clients should not be forced to depend upon interfaces that they do not use.)

  • Robert C. Martin

Bu prensibi daha iyi anlamak için anahtar kavramları açalım:

İstemci (Client): Bir sınıfın veya arayüzün hizmetlerini kullanan başka bir sınıf veya kod parçasıdır.
Arayüz (Interface): Bir sınıfın dış dünyaya sunduğu metotların (veya özelliklerin) bir koleksiyonudur. Python’da bu genellikle bir Soyut Temel Sınıf (ABC) veya bir Protokol ile temsil edilir, ancak bazen sadece bir sınıfın public metotları olarak da düşünülebilir.
Bağımlılık (Dependency): Bir kod parçasının (istemci), başka bir kod parçasının (arayüz/sınıf) çalışması için ona ihtiyaç duyması durumudur.
Zorlanmamalıdır: Prensibin özü buradadır. Eğer bir istemci, bağımlı olduğu arayüzdeki metotların sadece bir kısmını kullanıyorsa, kullanmadığı diğer metotların varlığı veya değişimi o istemciyi etkilememelidir.
“Şişman” Arayüz (Fat Interface / Interface Bloat) Problemi:

ISP’nin ele aldığı temel sorun, “şişman” veya “kabarık” arayüzlerdir. Bunlar, birbiriyle çok da ilişkili olmayan birden fazla farklı sorumluluğu veya yeteneği tek bir arayüz altında toplayan yapılardır. Böyle bir arayüzü implemente eden bir sınıf, belki de bu yeteneklerin sadece bir kısmına ihtiyaç duyar, ancak arayüz kontratına uymak için kullanmayacağı diğer metotları da (belki boş veya hata fırlatan şekilde) implemente etmek zorunda kalır.

Aynı şekilde, bu şişman arayüze bağımlı olan bir istemci, arayüzün sadece küçük bir alt kümesini kullansa bile, kullanmadığı bir metotta yapılan değişiklik nedeniyle yeniden derlenmek veya test edilmek zorunda kalabilir (statik dillerde daha belirgindir, ancak Python’da da dolaylı etkileri olabilir).

ISP’nin Çözüm Önerisi:

ISP, bu soruna çözüm olarak şunu önerir: Büyük, genel amaçlı arayüzler yerine, her biri belirli bir istemci grubunun ihtiyaçlarına veya belirli bir sorumluluk alanına odaklanan daha küçük, daha spesifik ve daha uyumlu (cohesive) arayüzler oluşturun. Sınıflar, sadece ihtiyaç duydukları bu küçük arayüzleri implemente ederler.

Analoji: Restoran Menüsü

Bir restoran düşünün. Eğer tek bir devasa menüsü olsaydı ve bu menüde kahvaltıdan akşam yemeğine, tatlılardan içeceklere, Türk mutfağından İtalyan mutfağına kadar her şey bulunsaydı, bu “şişman” bir menü olurdu. Sadece kahve içmek isteyen bir müşteri bile tüm bu seçenekleri görmek zorunda kalırdı. Garsonun (sınıfın) tüm bu yemekleri hazırlama potansiyeline (implementasyon zorunluluğuna) sahip olması beklenirdi. ISP ise daha çok ayrı menüler (kahvaltı menüsü, içecek menüsü, tatlı menüsü) kullanmaya benzer. Her menü (arayüz) belirli bir amaca hizmet eder. Müşteri (istemci) sadece ilgilendiği menüye bakar ve garson da sadece o menüdeki siparişleri hazırlama konusunda uzmanlaşabilir.

Bölüm 2: ISP Neden Önemli? Arayüzleri Ayırmanın Faydaları
Arayüz Ayrım Prensibi’ne uymanın getirdiği önemli avantajlar şunlardır:

Artan İçsel Uyum (Higher Cohesion):

Hem arayüzler hem de onları implemente eden sınıflar daha odaklanmış hale gelir. Küçük arayüzler, birbiriyle yakından ilişkili metotları bir araya getirir. Sınıflar da sadece gerçekten yerine getirdikleri sorumluluklarla ilgili metotları implemente ederler. Bu, kodun anlaşılabilirliğini ve tutarlılığını artırır.
Azaltılmış Bağlantı (Lower Coupling):

İstemciler, sadece ihtiyaç duydukları metotları içeren küçük arayüzlere bağımlı olurlar. Bu, istemcilerin bilmesi ve etkilenmesi gereken şeylerin sayısını azaltır. Arayüzün istemcinin kullanmadığı bir bölümünde yapılan değişiklik, istemciyi etkilemez. Bu da sistemi daha modüler ve değişime karşı daha dirençli hale getirir.
Geliştirilmiş Kod Yeniden Kullanılabilirliği:

Küçük, odaklanmış arayüzlerin ve sınıfların farklı bağlamlarda yeniden kullanılma olasılığı daha yüksektir.
Daha Kolay Implementasyon ve Bakım:

Sınıflar, kullanmayacakları metotları implemente etmek zorunda kalmazlar. Bu, gereksiz kod yazımını önler ve implementasyonu basitleştirir.
Bir arayüzde veya implementasyonda değişiklik yapılması gerektiğinde, etki alanı daha sınırlı olur, bu da bakımı kolaylaştırır.
Daha İyi Test Edilebilirlik:

Küçük arayüzlere bağımlı olan istemcileri test etmek daha kolaydır, çünkü testler için oluşturulması gereken sahte (mock) nesneler daha basittir (sadece ilgili küçük arayüzü implemente etmeleri yeterlidir).
Bölüm 3: ISP İhlali Örneği: “Şişman” Makine Arayüzü
Şimdi ISP’nin ihlal edildiği bir senaryoyu ve bunun olası sonuçlarını inceleyelim.

ISP İhlali — Şişman Arayüz

import abc

Çok fazla sorumluluğu olan tek bir "şişman" arayüz

class IMakine(abc.ABC):
@abc.abstractmethod
def yazdir(self, belge): pass
@abc.abstractmethod
def tara(self, belge): pass
@abc.abstractmethod
def faks_gonder(self, belge, numara): pass
@abc.abstractmethod
def zimbala(self, belge): pass # Tüm makineler zımbalamaz!

Bu arayüzü implemente etmeye çalışan sınıflar

class CokFonkMakine(IMakine):
def yazdir(self, belge): print(f"'{belge}' yazdırılıyor...")
def tara(self, belge): print(f"'{belge}' taranıyor...")
def faks_gonder(self, belge, numara): print(f"'{belge}' {numara}'ya fakslanıyor...")
def zimbala(self, belge): print(f"'{belge}' zımbalanıyor...")
# Bu sınıf tüm fonksiyonları kullandığı için sorun yok gibi görünüyor.
class EskiYazici(IMakine): # Sadece yazdırma yapabilen eski bir yazıcı
def yazdir(self, belge): print(f"[Eski Yazıcı] '{belge}' yazdırılıyor...")
# !!! ISP İHLALİ: Kullanmadığı metotları implemente etmek ZORUNDA !!!
def tara(self, belge):
# raise NotImplementedError("Tarama desteklenmiyor.") # Ya hata fırlatacak
print("Uyarı: Bu yazıcı tarama yapamaz.")
pass # Ya da hiçbir şey yapmayacak (istenmeyen durum)
def faks_gonder(self, belge, numara):
print("Uyarı: Bu yazıcı faks gönderemez.")
pass
def zimbala(self, belge):
print("Uyarı: Bu yazıcı zımbalayamaz.")
pass
class FaksMakinesi(IMakine): # Sadece faks ve belki yazdırma yapabilir
def yazdir(self, belge): print(f"[Faks Mak.] '{belge}' yazdırılıyor...")
def faks_gonder(self, belge, numara): print(f"[Faks Mak.] '{belge}' {numara}'ya fakslanıyor...")
# !!! ISP İHLALİ: Kullanmadığı metotları implemente etmek ZORUNDA !!!
def tara(self, belge): pass
def zimbala(self, belge): pass

İstemci Kodlar

def yazdirmayi_kullan(makine: IMakine, dokuman):
print("\nYazdırma işlemi deneniyor...")
makine.yazdir(dokuman) # Bu her zaman çalışmalı
def taramayi_kullan(makine: IMakine, dokuman):
print("\nTarama işlemi deneniyor...")
makine.tara(dokuman) # Bu, EskiYazici için anlamsız!
def fakslamayi_kullan(makine: IMakine, dokuman, num):
print("\nFakslama işlemi deneniyor...")
makine.faks_gonder(dokuman, num) # Bu, EskiYazici için anlamsız!

Kullanım

cok_fonk = CokFonkMakine()
eski_yaz = EskiYazici()
faks_cihaz = FaksMakinesi()
dok = "GizliRapor.docx"
yazdirmayi_kullan(cok_fonk, dok)
yazdirmayi_kullan(eski_yaz, dok)
yazdirmayi_kullan(faks_cihaz, dok)
taramayi_kullan(cok_fonk, dok)
taramayi_kullan(eski_yaz, dok) # Anlamsız bir çağrı, uyarı verir veya hata fırlatır (implementasyona bağlı)
taramayi_kullan(faks_cihaz, dok) # Anlamsız bir çağrı
fakslamayi_kullan(cok_fonk, dok, "12345")
fakslamayi_kullan(eski_yaz, dok, "12345") # Anlamsız bir çağrı
fakslamayi_kullan(faks_cihaz, dok, "12345")
Neden İhlal Ediliyor?
Gereksiz Bağımlılık: EskiYazici sınıfı, tara, faks_gonder, zimbala metotlarına hiçbir zaman ihtiyaç duymamasına rağmen, IMakine arayüzünü implemente ettiği için bu metotları tanımlamak zorunda kalıyor. Bu, gereksiz bir bağımlılık yaratır.
Anlamsız Implementasyonlar: EskiYazici içindeki kullanılmayan metotların implementasyonu ya boştur (pass) ya da bir hata fırlatır (NotImplementedError) veya bir uyarı basar. Bunların hiçbiri ideal değildir ve kodun amacını bulanıklaştırır.
İstemci Kodun Kırılganlığı: taramayi_kullan gibi bir istemci fonksiyonu, IMakine türünden bir nesne aldığında, o nesnenin gerçekten tarama yapabildiğini varsayar. Ancak EskiYazici nesnesi verildiğinde bu varsayım boşa çıkar ve program ya anlamsız bir işlem yapar ya da hata verir. İstemci, aldığı nesnenin gerçek yeteneklerini kontrol etmek zorunda kalabilir (bu da LSP ve OCP ihlallerine yol açabilir).
Düşük İçsel Uyum: IMakine arayüzü, birbiriyle ilgisiz olabilecek (yazdırma, tarama, fakslama, zımbalama) birçok farklı sorumluluğu bir araya getirdiği için düşük içsel uyuma sahiptir.
Bölüm 4: Çözüm: Arayüzleri Ayırmak (Segregation)
ISP’ye uygun çözüm, “şişman” IMakine arayüzünü, her biri belirli bir yeteneği temsil eden daha küçük, daha odaklanmış arayüzlere ayırmaktır.

ISP Uyumlu Çözüm — Ayrılmış Arayüzler

import abc

--- Ayrılmış, Odaklanmış Arayüzler (ABCs) ---

class IYazdirici(abc.ABC):
@abc.abstractmethod
def yazdir(self, belge): pass
class ITarayici(abc.ABC):
@abc.abstractmethod
def tara(self, belge): pass
class IFaksGonderici(abc.ABC):
@abc.abstractmethod
def faks_gonder(self, belge, numara): pass
class IZimbalayici(abc.ABC):
@abc.abstractmethod
def zimbala(self, belge): pass

--- Implemente Eden Sınıflar ---

Sınıflar SADECE ihtiyaç duydukları arayüzleri implemente ederler

class CokFonkMakine(IYazdirici, ITarayici, IFaksGonderici, IZimbalayici): # Çoklu kalıtım
def yazdir(self, belge): print(f"[Çok Fonk] '{belge}' yazdırılıyor...")
def tara(self, belge): print(f"[Çok Fonk] '{belge}' taranıyor...")
def faks_gonder(self, belge, numara): print(f"[Çok Fonk] '{belge}' {numara}'ya fakslanıyor...")
def zimbala(self, belge): print(f"[Çok Fonk] '{belge}' zımbalanıyor...")
class EskiYazici(IYazdirici): # Sadece IYazdirici implemente eder
def yazdir(self, belge): print(f"[Eski Yazıcı] '{belge}' yazdırılıyor...")
# Başka metot implemente etmek ZORUNDA DEĞİL!
class FaksCihazi(IFaksGonderici): # Sadece IFaksGonderici implemente eder (belki IYazdirici da olabilir)
def faks_gonder(self, belge, numara): print(f"[Faks Cihazı] '{belge}' {numara}'ya fakslanıyor...")
# İsterse yazdırma da ekleyebilir:
# class FaksCihazi(IFaksGonderici, IYazdirici):
# ...
# def yazdir(...): ...

--- İstemci Kodlar (Artık daha spesifik arayüzlere bağımlı olabilirler) ---

def belgeyi_yazdir(yazici_nesnesi: IYazdirici, dokuman): # Sadece IYazdirici bekliyor
print("\nYazdırma işlemi (ISP Uyumlu)...")
if isinstance(yazici_nesnesi, IYazdirici):
yazici_nesnesi.yazdir(dokuman)
else:
print("Hata: Nesne yazdırma yeteneğine sahip değil.")
def belgeyi_tara(tarayici_nesnesi: ITarayici, dokuman): # Sadece ITarayici bekliyor
print("\nTarama işlemi (ISP Uyumlu)...")
if isinstance(tarayici_nesnesi, ITarayici):
tarayici_nesnesi.tara(dokuman)
else:
print("Hata: Nesne tarama yeteneğine sahip değil.")

Kullanım

cok_fonk = CokFonkMakine()
eski_yaz = EskiYazici()
faks = FaksCihazi()
dok = "onemli_sunum.pptx"
belgeyi_yazdir(cok_fonk, dok)
belgeyi_yazdir(eski_yaz, dok)

belgeyi_yazdir(faks, dok) # FaksCihazi IYazdirici implemente etmediği için hata verir (veya isinstance False döner)

belgeyi_tara(cok_fonk, dok)

belgeyi_tara(eski_yaz, dok) # Hata verir (veya isinstance False döner)

belgeyi_tara(faks, dok) # Hata verir (veya isinstance False döner)

Sadece faks gönderebilen bir istemci:

def faks_gonderici_kullan(faks_nesnesi: IFaksGonderici, ...): ...

Çözümün Faydaları:
Odaklanmış Arayüzler: Her arayüz (IYazdirici, ITarayici vb.) tek bir yeteneğe odaklanır.
Gereksiz Implementasyon Yok: EskiYazici sadece IYazdirici'yı implemente eder, ihtiyaç duymadığı tara, faks_gonder gibi metotları tanımlamak zorunda kalmaz.
Daha Net Bağımlılıklar: İstemci kod (belgeyi_yazdir, belgeyi_tara) artık tüm makine fonksiyonlarını içeren devasa bir arayüze değil, sadece ihtiyaç duyduğu spesifik yeteneği tanımlayan küçük arayüze (IYazdirici, ITarayici) bağımlıdır.
Artan Esneklik: Yeni bir makine türü (örn: sadece tarayıcı) eklemek veya mevcut bir makinenin yeteneklerini değiştirmek (örn: FaksCihazi’na yazdırma eklemek) daha kolaydır ve diğer bileşenleri daha az etkiler.
İyileştirilmiş Test Edilebilirlik: İstemci kodu test ederken, sadece ihtiyaç duyulan küçük arayüzü sağlayan sahte nesneler oluşturmak yeterlidir.
Bölüm 5: ISP ve Diğer Tasarım Prensipleri
Arayüz Ayrım Prensibi, diğer tasarım prensipleriyle (özellikle SOLID içindekilerle) yakından ilişkilidir ve onları destekler:

Tek Sorumluluk Prensibi (SRP): ISP, SRP’yi arayüz seviyesine uygular. Şişman arayüzler genellikle birden fazla sorumluluğu birleştirdiği için SRP’yi ihlal eder. Arayüzleri ayırmak, her arayüzün tek bir sorumluluğa odaklanmasını sağlar.
Açık/Kapalı Prensibi (OCP): Küçük, odaklanmış arayüzler kullanmak, OCP’yi uygulamayı kolaylaştırır. Yeni işlevsellikler genellikle mevcut arayüzleri değiştirmek yerine yeni arayüzler ekleyerek veya mevcut küçük arayüzleri implemente eden yeni sınıflar oluşturarak sağlanabilir.
Liskov Yerine Geçme Prensibi (LSP): Bir sınıfın, kullanmadığı metotları içeren şişman bir arayüzü implemente etmesi (ve bu metotları boş veya hata fırlatan şekilde bırakması), LSP ihlaline yol açabilir. Çünkü bu alt sınıf, arayüzün tüm kontratını davranışsal olarak yerine getiremez. ISP, sınıfların sadece gerçekten sağladıkları yetenekleri tanımlayan arayüzleri implemente etmesini sağlayarak LSP’ye uyumu destekler.
Bağımlılığın Tersine Çevrilmesi Prensibi (DIP): DIP, yüksek seviyeli modüllerin düşük seviyeli modüllere değil, soyutlamalara bağımlı olması gerektiğini söyler. ISP, bu soyutlamaların (arayüzlerin) “şişman” olmamasını, istemcilerin sadece ihtiyaç duydukları metotları içeren küçük arayüzlere bağımlı olmasını sağlayarak DIP’in etkinliğini artırır.
Bölüm 6: Python’da Pratik Uygulama İpuçları
ABC’leri Kullanın: Python’da resmi arayüzler tanımlamak ve implementasyon zorunluluğu getirmek için abc modülünü ve @abstractmethod dekoratörünü kullanın.
Rol Odaklı Arayüzler Tasarlayın: Bir sınıfın sahip olabileceği farklı “rolleri” düşünün (örn: Yazdırılabilir, Sıralanabilir, Kaydedilebilir, Doğrulanabilir) ve her rol için ayrı bir ABC veya Protokol tanımlayın. Sınıflarınız sadece üstlendikleri rollere karşılık gelen arayüzleri implemente etsin.
Protokolleri Değerlendirin (Python 3.8+): Eğer kalıtım ilişkisi kurmak istemiyorsanız veya mevcut sınıfları değiştiremiyorsanız, yapısal uyumluluğa dayalı typing.Protocol kullanmak daha esnek bir alternatif olabilir.
İstemci Odaklı Düşünün: Arayüzleri tasarlarken, onları kimin kullanacağını (istemcileri) düşünün. İstemcinin gerçekten neye ihtiyacı var? Sadece bu ihtiyaçları karşılayan minimal arayüzler oluşturmaya çalışın.
Kalıtımı Dikkatli Kullanın: Çoklu kalıtımı, özellikle farklı sorumluluk alanlarını temsil eden küçük ABC’leri (Mixin benzeri) birleştirmek için kullanabilirsiniz, ancak büyük, şişman temel sınıflardan kaçının.
Bölüm 7: Sonuç: Odaklanmış Arayüzlerin Gücü
Arayüz Ayrım Prensibi (ISP), yazılım tasarımında önemli bir ilkedir ve bize şunu hatırlatır: İhtiyaç duyulmayan bağımlılıklardan kaçının. Büyük, her işi yapan arayüzler yerine, küçük, spesifik ve istemci odaklı arayüzler tasarlamak, kodumuzun kalitesini önemli ölçüde artırır.

ISP’ye uymanın temel faydaları şunlardır:

Daha yüksek içsel uyum ve daha düşük bağlantı.
Artan modülerlik ve yeniden kullanılabilirlik.
Daha kolay implementasyon, bakım ve test etme.
Diğer SOLID prensiplerine (özellikle OCP, LSP, DIP) uyumu destekleme.
Python’da doğrudan interface anahtar kelimesi olmasa da, Soyut Temel Sınıflar (ABCs) ve Protokoller, ISP'yi uygulamak için etkili mekanizmalar sunar. Bu araçları kullanarak ve arayüzleri dikkatlice ayırarak, daha esnek, daha sağlam ve değişime daha kolay adapte olabilen yazılım sistemleri oluşturabiliriz. ISP, büyük ve karmaşık sistemlerde bile kodun anlaşılabilir ve yönetilebilir kalmasını sağlayan önemli bir tasarım tekniğidir.

Abdulkadir Güngör - Kişisel WebSite
Abdulkadir Güngör - Kişisel WebSite
Abdulkadir Güngör - Özgeçmiş
Github
Github
Linkedin