Yazılım tasarımında sağlamlık, esneklik ve sürdürülebilirlik hedeflerine ulaşmak için Nesne Yönelimli Programlama (OOP) bize yol gösteren temel ilkeler sunar. SOLID prensiplerinden biri olan Arayüz Ayrım Prensibi (Interface Segregation Principle — ISP), özellikle sınıflar arası etkileşimlerin ve bağımlılıkların nasıl yönetilmesi gerektiğine odaklanır. ISP’nin temel mesajı nettir: İstemciler, kullanmadıkları metotlara sahip arayüzlere bağımlı olmaya zorlanmamalıdır.

Bu prensip, “şişman arayüzler” (fat interfaces) olarak adlandırılan, birbiriyle ilgisiz veya tüm istemcilerin ihtiyaç duymadığı birçok metodu tek bir çatı altında toplayan arayüz tasarımlarından kaçınmamız gerektiğini söyler. Şişman arayüzler, kodda gereksiz bağımlılıklara, düşük içsel uyuma (cohesion), artan karmaşıklığa ve bakım zorluklarına yol açabilir.

Python, doğrudan bir interface anahtar kelimesi içermese de, ISP'nin altında yatan fikirler Python kodu için de son derece geçerlidir. Python'da bu prensibi uygulamak için Soyut Temel Sınıflar (ABCs) ve Protokoller gibi mekanizmalar kullanılır. Bu rehberde, "şişman arayüz" problemini detaylıca inceleyecek, ISP'nin tam olarak ne önerdiğini açıklayacak, bu prensibe uymanın faydalarını vurgulayacak ve Python'da ISP ihlallerinden kaçınarak nasıl daha küçük, odaklanmış ve müşteriye özel arayüzler tasarlayabileceğimizi somut örneklerle göstereceğiz.

Bölüm 1: Sorun: “Şişman” Arayüzler (Fat Interfaces) Nedir?
ISP’yi anlamak için önce çözmeye çalıştığı problemi, yani “şişman arayüzleri” anlamamız gerekir. Bir şişman arayüz, tek bir arayüz tanımı altında çok fazla sayıda metot barındıran, genellikle birden fazla farklı sorumluluğu veya yeteneği birleştiren bir arayüzdür.

Özellikleri:

Geniş Kapsamlıdır: Tek bir arayüz, birbiriyle sadece gevşek bir şekilde ilişkili veya tamamen ilişkisiz birçok farklı işlemi tanımlayabilir.
Düşük İçsel Uyum (Low Cohesion): Arayüzdeki metotlar tek bir, net bir amaca hizmet etmezler; farklı sorumluluk alanlarına dağılmışlardır.
Her Duruma Uyan (One-size-fits-all) Yaklaşımı: Tek bir arayüzün, potansiyel olarak çok farklı ihtiyaçlara sahip olabilecek tüm istemcilere hizmet etmesi beklenir.
Neden Ortaya Çıkarlar?

Başlangıç Tasarımı: Projenin başında tüm olası ihtiyaçları tek bir genel arayüzde toplamak daha kolay görünebilir.
Evrimsel Büyüme: Zamanla yeni metotlar sürekli olarak mevcut bir arayüze eklenir ve arayüz giderek şişmanlar.
Soyutlama Eksikliği: Farklı sorumluluk alanları arasındaki sınırların net bir şekilde çizilmemesi.
Analoji: Evrensel Uzaktan Kumanda

Hayal edin ki evinizdeki tüm cihazları (TV, müzik seti, klima, aydınlatma, garaj kapısı) kontrol eden tek bir devasa uzaktan kumandanız var. Bu kumanda üzerinde yüzlerce düğme bulunuyor. Bu, bir “şişman arayüz” gibidir. Eğer sadece televizyonun sesini açmak istiyorsanız (tek bir istemci ihtiyacı), yine de klima, garaj kapısı gibi kullanmadığınız birçok düğmenin bulunduğu karmaşık bir arayüze maruz kalırsınız. Kumandanın klima ile ilgili bir düğmesi bozulsa bile (arayüzün kullanmadığınız bir kısmında değişiklik), bu durum sizi dolaylı olarak etkileyebilir (örneğin, kumandanın genel işleyişi bozulursa). ISP, bunun yerine her cihaz grubu için ayrı, daha basit kumandalar (odaklanmış arayüzler) olmasını önerir.

“Şişman” Arayüzlerin Problemleri Nelerdir?
Şişman arayüzler kullanmanın getirdiği temel sorunlar şunlardır:

Gereksiz Implementasyon Zorunluluğu: Bir sınıf, şişman bir arayüzü implemente ettiğinde, arayüzdeki metotların sadece bir kısmına ihtiyaç duysa bile, genellikle tüm metotları (en azından boş veya hata fırlatan şekilde) implemente etmek zorunda kalır. Bu, gereksiz kod ve karmaşıklık yaratır.
Artan Bağımlılık (Coupling): İstemciler, aslında ihtiyaç duymadıkları metotları da içeren bir arayüze bağımlı hale gelirler. Bu, istemci ile arayüz arasında gereksiz bir sıkı bağlantı oluşturur.
Değişikliklerin Yayılma Etkisi (Ripple Effect): Şişman arayüzün bir istemcinin kullanmadığı bir bölümünde (örneğin, kullanılmayan bir metodun imzasında) yapılan bir değişiklik, yine de o arayüze bağımlı olan tüm istemcilerin yeniden derlenmesini (statik dillerde) veya en azından potansiyel olarak etkilenmesini (dinamik dillerde davranış değişikliği riski) gerektirebilir.
Düşük Okunabilirlik ve Anlaşılabilirlik: Bir sınıfın hangi arayüz metotlarını gerçekten anlamlı bir şekilde implemente ettiğini anlamak zorlaşır. Arayüzün kendisinin amacı da bulanıklaşır.
Zorlaşan Test Edilebilirlik: İstemci kodu test ederken, şişman arayüzü taklit eden (mock) nesneler oluşturmak daha karmaşık hale gelir, çünkü kullanılmayan metotların bile bir şekilde tanımlanması gerekebilir.
Bölüm 2: Arayüz Ayrım Prensibi (ISP) Detaylı Açıklama
Prensip
İstemciler (clients), kullanmadıkları metotlara sahip arayüzlere bağımlı olmaya zorlanmamalıdır.

ISP’nin temel mesajı, arayüzleri istemcilerin ihtiyaçlarına göre ayırmaktır. Tek, monolitik bir arayüz yerine, her biri belirli bir rolü veya yetenek grubunu temsil eden birden fazla küçük ve uyumlu (cohesive) arayüz oluşturulmalıdır.

Uygulama Adımları:

Arayüzdeki Sorumlulukları Belirleyin: Mevcut veya tasarlanan bir arayüzdeki metotları inceleyin. Bu metotlar farklı istemci grupları tarafından mı kullanılıyor? Farklı sorumluluk alanlarını mı temsil ediyorlar?
Arayüzleri Ayırın: Birbiriyle yakından ilişkili ve genellikle birlikte kullanılan metotları daha küçük, daha odaklanmış arayüzler altında gruplayın. Her yeni arayüz, belirli bir rolü veya yeteneği temsil etmelidir.
Sınıfların Sadece Gerekli Arayüzleri Implemente Etmesini Sağlayın: Sınıflar, tüm yetenekleri içeren tek bir şişman arayüz yerine, sadece kendi sağladıkları işlevselliğe karşılık gelen küçük arayüzleri implemente etmelidir. Python’da bu, genellikle birden fazla ABC’den miras alma veya birden fazla Protokole uyma anlamına gelir.
İstemcilerin Spesifik Arayüzlere Bağımlı Olmasını Sağlayın: İstemci kodlar, işlerini yapmak için gereken minimum metot setini içeren en spesifik arayüze bağımlı olmalıdır. Tüm arayüze değil.
Örnek: “Şişman” IMakine arayüzümüz (Yazdır, Tara, Faksla, Zımbala) yerine, aşağıdaki gibi ayrı arayüzler tanımlarız:

IYazdirici { yazdir() }
ITarayici { tara() }
IFaksGonderici { faks_gonder() }
IZimbalayici { zimbala() }
Artık EskiYazici sınıfı sadece IYazdirici'yı implemente eder. CokFonkMakine ise hepsini implemente edebilir. belgeyi_tara fonksiyonu ise artık IMakine'ye değil, sadece ITarayici arayüzüne bağımlı olur.

Bölüm 3: ISP Uygulama Örnekleri (Python)
Şimdi, önceki bölümde ISP ihlalini gösteren “şişman” IMakine arayüzü örneğini alıp, ISP’ye uygun hale getiren çözümü daha detaylı inceleyelim.

Örnek: Makine Arayüzünün Ayrıştırılması
ISP İhlali (Tekrar)

Tek, "ş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
class EskiYazici(IMakine):
def yazdir(self, belge): print(f"[Eski Yazıcı] '{belge}' yazdırılıyor...")
# Diğerlerini boş/hata ile implemente etmek zorunda!
def tara(self, belge): pass
def faks_gonder(self, belge, numara): pass
def zimbala(self, belge): pass

İstemci (potansiyel olarak yanlış çalışabilir)

def tarama_yap(makine: IMakine, belge):
makine.tara(belge) # EskiYazici için bu çağrı anlamsız!
ISP Uyumlu Çözüm (Ayrılmış Arayüzler — ABCs)

import abc

--- Adım 1 & 2: Arayüzleri Ayır ---

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

(Zimbalayici arayüzü de benzer şekilde tanımlanabilir)

--- Adım 3: Sınıflar Sadece Gerekli Arayüzleri Implemente Etsin ---

class CokFonkMakine(IYazdirici, ITarayici, IFaksGonderici): # Sadece ihtiyaç duyulanlar
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...")
# Zimbala implementasyonu yoksa IZimbalayici'dan miras almaz
class EskiYazici(IYazdirici): # Sadece yazdırma yeteneği var
def yazdir(self, belge): print(f"[Eski Yazıcı] '{belge}' yazdırılıyor...")
# Başka bir şey implemente etmek zorunda değil!
class SadeceTarayici(ITarayici): # Sadece tarama yeteneği var
def tara(self, belge): print(f"[Tarayıcı] '{belge}' taranıyor...")

--- Adım 4: İstemciler Spesifik Arayüzlere Bağımlı Olsun ---

def yazdirmayi_kullan(yazici_cihaz: IYazdirici, dokuman): # Artık IMakine değil, IYazdirici bekliyor
print("\nYazdırma işlemi (ISP Uyumlu)...")
yazici_cihaz.yazdir(dokuman)
def taramayi_kullan(tarayici_cihaz: ITarayici, dokuman): # Artık IMakine değil, ITarayici bekliyor
print("\nTarama işlemi (ISP Uyumlu)...")
tarayici_cihaz.tara(dokuman)

Kullanım

cok_fonk_cihaz = CokFonkMakine()
eski_yaz_cihaz = EskiYazici()
tarayici_cihaz = SadeceTarayici()
dok = "rapor_v2.pdf"
yazdirmayi_kullan(cok_fonk_cihaz, dok)
yazdirmayi_kullan(eski_yaz_cihaz, dok)

yazdirmayi_kullan(tarayici_cihaz, dok) # TypeError veya AttributeError verir çünkü tarayıcı IYazdirici değil

taramayi_kullan(cok_fonk_cihaz, dok)

taramayi_kullan(eski_yaz_cihaz, dok) # Hata verir çünkü eski yazıcı ITarayici değil

taramayi_kullan(tarayici_cihaz, dok)
Çözümün Analizi:
Ayrılmış Arayüzler: Tek, şişman IMakine arayüzü yerine, her biri belirli bir yeteneğe odaklanan IYazdirici, ITarayici, IFaksGonderici gibi küçük arayüzler oluşturuldu.
Odaklanmış Implementasyonlar: EskiYazici sınıfı artık sadece ihtiyaç duyduğu IYazdirici arayüzünü implemente ediyor ve gereksiz tara, faks_gonder metotlarını tanımlamak zorunda kalmıyor.
Spesifik Bağımlılıklar: yazdirmayi_kullan fonksiyonu artık genel IMakine yerine spesifik olarak IYazdirici arayüzüne bağımlı. Bu sayede kendisine verilen nesnenin gerçekten yazdırma yeteneğine sahip olduğundan (kontrat gereği) emin olabilir. Benzer şekilde taramayi_kullan da ITarayici'ya bağımlı.
Artan Netlik: Kodun amacı daha net hale geldi. Hangi sınıfın hangi yeteneklere sahip olduğu ve hangi istemcinin hangi yeteneği beklediği daha açık.
Gevşek Bağlantı: yazdirmayi_kullan fonksiyonu, tarama veya faksla ilgili arayüzlerdeki değişikliklerden artık etkilenmez.
Bölüm 5: ISP Uygulamasında Protokollerin Rolü (Python 3.8+)
Python 3.8 ve sonrası için, Arayüz Ayrım Prensibi’ni uygulamanın bir başka yolu da typing.Protocol kullanmaktır. Protokoller, ABC'ler gibi açıkça miras alınmayı gerektirmez; bir sınıfın bir protokole uyup uymadığı yapısal olarak (yani gerekli metotlara sahip olup olmadığına bakılarak) belirlenir.

Bu, özellikle mevcut sınıfları değiştiremediğimiz veya kalıtım hiyerarşisi kurmak istemediğimiz durumlarda ISP uygulamak için esnek bir yol sunar.

ISP Uyumlu Çözüm (Protokoller ile)

from typing import Protocol, runtime_checkable

--- Ayrılmış, Odaklanmış Protokoller ---

@runtime_checkable # isinstance ile kontrol için (isteğe bağlı)
class Yazabilir(Protocol):
def yazdir(self, belge: str) -> None: ...
@runtime_checkable
class Tarayabilir(Protocol):
def tara(self, belge: str) -> bytes: ... # Örneğin taranan veriyi byte olarak döndürsün

--- Implemente Eden Sınıflar (Protokolden miras ALMIYORLAR) ---

class LaserYazici:
def yazdir(self, belge: str) -> None:
print(f"[LaserYazici] '{belge}' yüksek hızda yazdırılıyor...")
class FlatbedTarayici:
def tara(self, belge: str) -> bytes:
print(f"[FlatbedTarayici] '{belge}' taranıyor...")
return f"Taranan veri: {belge}".encode('utf-8')
class MurekkepPuskurtmeliYaziciTarayici: # Hem yazdırır hem tarar
def yazdir(self, belge: str) -> None:
print(f"[Mürekkep] '{belge}' yazdırılıyor...")
def tara(self, belge: str) -> bytes:
print(f"[Mürekkep] '{belge}' taranıyor...")
return f"Taranan veri (renkli): {belge}".encode('utf-8')

--- İstemci Kodlar (Spesifik Protokollere Bağımlı) ---

def baski_gonder(cihaz: Yazabilir, dokuman: str): # Yazabilir protokolünü bekliyor
print("\nBaskı gönderiliyor (Protokol)...")
# Tip kontrolü (MyPy gibi araçlar bunu derleme/analiz zamanında yapar)
# Runtime kontrolü için @runtime_checkable ve isinstance gerekir:
if isinstance(cihaz, Yazabilir):
cihaz.yazdir(dokuman)
else:
print("Hata: Cihaz 'Yazabilir' protokolüne uymuyor.")
def tarama_baslat(cihaz: Tarayabilir, dokuman: str): # Tarayabilir protokolünü bekliyor
print("\nTarama başlatılıyor (Protokol)...")
if isinstance(cihaz, Tarayabilir):
taranan_veri = cihaz.tara(dokuman)
print(f"Tarama tamamlandı, alınan veri boyutu: {len(taranan_veri)} bytes")
else:
print("Hata: Cihaz 'Tarayabilir' protokolüne uymuyor.")

Kullanım

laser = LaserYazici()
tarayici = FlatbedTarayici()
hepsi_bir_arada = MurekkepPuskurtmeliYaziciTarayici()
doc = "FinansRaporu.xlsx"
baski_gonder(laser, doc)
baski_gonder(hepsi_bir_arada, doc)

baski_gonder(tarayici, doc) # MyPy hata verir, isinstance False döner

tarama_baslat(tarayici, doc)
tarama_baslat(hepsi_bir_arada, doc)

tarama_baslat(laser, doc) # MyPy hata verir, isinstance False döner

Protokol Yaklaşımının Değerlendirmesi:
ISP Sağlandı mı? Evet. İstemciler (baski_gonder, tarama_baslat) sadece ihtiyaç duydukları metotları içeren protokollere (Yazabilir, Tarayabilir) bağımlıdır.
Esneklik: Sınıfların (LaserYazici, FlatbedTarayici) protokolden açıkça miras almasına gerek yoktur. Sadece gerekli metotları doğru imzayla tanımlamaları yeterlidir (yapısal uyumluluk). Bu, mevcut kod tabanlarına veya farklı kütüphanelerden gelen sınıflara protokolleri uygulamayı kolaylaştırır.
Tip Güvenliği: Protokoller, MyPy gibi statik tip denetleyicileri tarafından kullanılarak, bir nesnenin beklenen arayüze uyup uymadığının analiz zamanında kontrol edilmesini sağlar.
Runtime Kontrolü: isinstance() ile runtime kontrolü yapmak için protokolün @runtime_checkable ile işaretlenmesi gerekir ve bu ABC'lere göre biraz daha az yaygın veya bazen daha az performanslı olabilir.
Bölüm 6: Sonuç: İstemci Odaklı Arayüzler Tasarlamak
Arayüz Ayrım Prensibi (ISP), Nesne Yönelimli Tasarımda istemci odaklı düşünmenin önemini vurgular. Büyük, monolitik ve her şeyi yapmaya çalışan “şişman” arayüzler yerine, istemcilerin özel ihtiyaçlarına göre ayrıştırılmış, küçük ve odaklanmış arayüzler oluşturmayı teşvik eder.

Şişman arayüzlerden kaçınmanın temel faydaları şunlardır:

Azaltılmış Bağımlılıklar: İstemciler sadece ihtiyaç duydukları işlevselliğe bağımlı olur.
Artan İçsel Uyum: Hem arayüzler hem de sınıflar daha odaklı hale gelir.
Daha Kolay Implementasyon: Sınıflar gereksiz metotları implemente etmek zorunda kalmaz.
Geliştirilmiş Esneklik ve Bakım Kolaylığı: Değişikliklerin etkisi sınırlanır.
Daha İyi Test Edilebilirlik: Hem istemcileri hem de implementasyonları test etmek kolaylaşır.
Python’da ISP’yi uygulamak için:

Soyut Temel Sınıfları (ABCs) Kullanın: Ortak bir temel yapı veya implementasyon paylaşan ve belirli bir kontratı zorunlu kılmak istediğiniz kalıtım hiyerarşileri için idealdir.
Protokolleri (typing.Protocol) Kullanın: Kalıtımdan bağımsız, yapısal uyumluluğa dayalı arayüzler tanımlamak ve statik tip kontrolünden yararlanmak için modern ve esnek bir yoldur.
Duck Typing’i Dikkatli Kullanın: En esnek yaklaşım olsa da, kontrat garantisi vermez ve hatalar çalışma zamanında ortaya çıkabilir. Basit ve iyi anlaşılmış arayüzler için uygundur.
Arayüzleri istemcilerin perspektifinden düşünerek ve onları uygun şekilde ayırarak, daha modüler, daha esnek, daha sağlam ve sonuçta daha başarılı yazılım sistemleri tasarlayabilirsiniz. ISP, SOLID prensipleri içinde kodun bağlantılarını (coupling) azaltmaya ve içsel uyumunu (cohesion) artırmaya doğrudan katkıda bulunan önemli bir ilkedir.

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