ISP (Interface Segregation Principle), SOLID'in dördüncü ilkesidir ve şunu belirtir: istemciler kullanmadıkları yöntemlere bağımlı olmamalıdır. İlke, Robert Martin tarafından nesne yönelimli sistemler için arayüz tasarımı bağlamında formüle edilmiştir. Clean Architecture (2017) kitabında açıklandığı gibi, arayüz ayrıştırma ilkesi tek bir evrensel arayüz yerine dar uzmanlaşmış arayüzler oluşturmayı gerektirir, bu da bağımlılığı azaltır ve değişiklik yapmayı basitleştirir.
Önemli Noktalar
ISP (Interface Segregation Principle), tüm istemciler tarafından kullanılmayan yöntemlerle “şişman” arayüzler oluşturmayı yasaklayan arayüz ayrıştırma ilkesidir. Bir düzine yöntemi olan tek bir arayüz yerine, her biri kendi istemci grubu için birkaç küçük arayüz tasarlanır.
İlke, Robert Martin tarafından “arayüz kirliliği” sorununa bir çözüm olarak tanıtılmıştır; bir sınıf, ortak bir arayüzde bildirildikleri için ihtiyaç duymadığı yöntemleri uygulamak zorunda kaldığında. Statik olarak türlenmiş dillerde bu, boş uygulamalara veya istisna fırlatmaya yol açar — ISP ihlalinin doğrudan bir işareti.
ISP ve SRP birbirini tamamlar: SRP sınıfın sorumluluğuyla ilgilidir, ISP arayüz sözleşmeleriyle ilgilidir. SRP “bir sınıf — değişiklik için bir neden” der, ISP “bir arayüz — bir istemci senaryosu” der. Birlikte, sistemin her öğesinin net sınırları olduğu modüler bir mimari oluştururlar.
Fat Interface — belirli bir istemcinin ihtiyaç duyduğundan daha fazla yöntem içeren bir arayüz. Örneğin, work, eat, sleep yöntemleriyle bir Worker arayüzü. Bir robot işçi eat ve sleep'i uygulamamalıdır, ancak uygulamak zorunda kalır. Çözüm, Workable, Eatable, Sleepable olarak bölmektir. Her istemci tam olarak ihtiyacı olanı alır.
Mobil geliştirmede, şişman arayüzler temsilci protokollerinde ve DataSource'da bulunur. Bir protokol, iki farklı senaryo (düzenleme + görüntüleme) için yöntemler içerebilir, ancak belirli bir ekran bunlardan yalnızca birini kullanır.
ISP'yi uygulamak, her arayüzün istemcilerini analiz etmekle başlar. İki istemci aynı arayüzün farklı yöntem kümelerini kullanıyorsa, arayüz bölünmelidir. Her yeni arayüz, tek bir senaryo içinde birlikte çağrılan yöntemleri gruplar.
Bölme mekanizması: orijinal arayüz, her biri ortak parçayı (varsa) miras alan birkaç dar arayüze ayrılır. İstemciler, genel arayüz yerine gerekli dar arayüze bağımlı olmaya geçer. Orijinal arayüzü uygulayan sınıflar artık yalnızca gerçekten ihtiyaç duydukları dar arayüzleri uygular.
Önemli bir açıklama: bölme derecesi, istemci sayısı ve senaryoları tarafından belirlenir. ISP maksimum bölme (her biri bir yönteme sahip mikro arayüzler) gerektirmez. Bu aşırı karmaşıklığa yol açar. Amaç, istemcilerin gereksiz yöntemlere bağımlılığını ortadan kaldırmaktır, her arayüzün boyutunu küçültmek değil.
ISP ihlalinin ana belirtileri şunları içerir: boş yöntemlerle (sahte uygulama) bir arayüz uygulayan sınıflar, uygulamalarda UnsupportedOperationException fırlatma, bazı istemciler tarafından kullanılmayan çok sayıda parametre veya dönüş türü ve yalnızca bazı istemcileri etkileyen sık arayüz değişiklikleri.
Android geliştirmede, ISP ihlalinin tipik bir örneği, tıklama, uzun tıklama ve kaydırma için yöntemler içeren OnItemClickListener arayüzüdür. Belirli bir ekran yalnızca tıklamayı kullanıyorsa, kalan yöntemler boş kalır. Çözüm, OnItemClickListener, OnItemLongClickListener, OnItemSwipeListener olarak bölmektir.
iOS geliştirmede, ISP ihlali UIKit temsilcilerinde kendini gösterir: bir protokol, farklı bileşen durumları için yöntemler içerir. UITableViewDelegate, görüntüleme, seçim, düzenleme ve kaydırma eylemi için yöntemler içerir. Geliştiriciler genellikle bir düzine boş yöntemle tüm protokolü uygular. Sorumluluk gruplarına göre birden çok protokole bölmek sorunu çözer.
Sorun yalnızca kod estetiğiyle ilgili değildir. Bir arayüz değiştiğinde (yeni bir yöntem eklendiğinde), tüm uygulayan sınıflar güncellenmelidir — yeni yönteme ihtiyaç duymayanlar bile. Düzinelerce ekranı olan mobil geliştirmede bu, kademeli değişikliklere yol açar. ISP, her istemciyi onu ilgilendirmeyen değişikliklerden yalıtır.
Örtülü ISP ihlali, yapılandırma parametreleri aracılığıyla gerçekleşir. Bir yöntem, çok sayıda alanı olan bir nesneyi kabul ediyorsa ve istemci bunlardan yalnızca 2-3'ünü kullanıyorsa — bu bölme sinyalidir. Alternatif: minimum parametre setine sahip birden çok uzmanlaşmış yöntem.
Android geliştirmede, tüm uygulama ayarlarını okumak ve yazmak için tek bir SharedPreferencesManager kullanıldığında ISP ihlal edilir. Yalnızca temayı okuması gereken bir Fragment, farklı veri türleri için bir düzine yöntemi olan küresel bir yöneticiye bağımlılık kazanır. ThemePreferenceProvider, AuthPreferenceProvider, FeatureFlagProvider'a bölme — yapılandırma hizmeti düzeyinde ISP uygulaması. Her sağlayıcı, istemcilerinin ihtiyaç duyduğu yöntemleri tam olarak içerir.
Verilerle çalışmak için bir arayüz kullanan bir Android örneği düşünelim. ISP ihlali — tüm CRUD işlemleri için tek bir arayüz, ancak tüm istemcilerin tüm işlemlere ihtiyacı yok.
// ISP İhlali: Şişman Arayüz
interface UserRepository {
fun getAll(): List<User>
fun getById(id: Int): User
fun save(user: User)
fun delete(id: Int)
}
// ISP Uygulandıktan Sonra: Dar Arayüzler
interface UserReader {
fun getAll(): List<User>
fun getById(id: Int): User
}
interface UserWriter {
fun save(user: User)
fun delete(id: Int)
}
// ReadOnlyViewModel, yazma yöntemlerine bağımlı değildir
class ReadOnlyViewModel(
private val reader: UserReader
)
Medyayla çalışmak için protokol ayrıştırması kullanan bir iOS örneği:
// ISP İhlali: Tüm Medya Çalışması İçin Tek Protokol
protocol MediaService {
func play(url: URL)
func pause()
func stop()
func upload(data: Data) async -> URL
func download(url: URL) async -> Data
}
// ISP'den Sonra: Sorumluluğa Göre Protokollere Ayrıştırma
protocol MediaPlayer {
func play(url: URL)
func pause()
func stop()
}
protocol MediaTransfer {
func upload(data: Data) async -> URL
func download(url: URL) async -> Data
}
// PlayerViewModel, indirme yöntemlerine bağımlı değildir
class PlayerViewModel {
private let player: MediaPlayer
}
Pratik sonuç: ISP, istemcileri arayüzün ilgisiz bölümlerindeki değişikliklerden korur. UserRepository'yi UserReader ve UserWriter'a bölmek, save'deki değişikliklerin ReadOnlyViewModel'i etkilemediği ve bunun tersi anlamına gelir. Her istemci kullanmadığı işlevsellikten yalıtılmıştır ve sistemin diğer bölümleri değiştirildiğinde değişiklik gerektirmez.
ISP ve SRP — doğal bir çift. SRP, bir sınıfın değişiklik için tek bir nedeni olması gerektiğini tanımlar. ISP aynı mantığı arayüzlere uygular: bir arayüz bir istemci senaryosuna hizmet etmelidir. Bir sınıf, birden çok dar arayüz uygulayabilir (her biri bir sorumluluğa karşılık gelir), bu da birden çok sorumluluğa sahip tek bir şişman arayüzden daha temizdir.
ISP ve OCP de ilişkilidir: dar arayüzler genişletilmesi daha kolaydır. Dar bir arayüze yeni bir yöntem eklemek yalnızca istemcilerini etkiler. Şişman bir arayüze yöntem eklemek tüm istemcileri etkiler — istemciler uygulamalarını değiştirmek zorunda kalırsa potansiyel olarak OCP'yi ihlal eder.
ISP ve DIP birlikte çalışır: DIP, soyutlamalara bağımlılık gerektirir. ISP bu soyutlamaları dar ve odaklanmış hale getirir. Geniş bir arayüze bağımlı olmak hâlâ bir soyutlamaya bağımlılıktır, ancak ISP perspektifinden “kötü” bir soyutlamadır. Dört ilke (SRP, OCP, ISP, DIP), “modülerlik piramidini” oluşturur: SRP ve ISP sınırları tanımlar, OCP ve DIP genişletme ve bağlama yollarını tanımlar.
Mobil projelerde (modüller, özellikler, katmanlar) bileşen mimarisi, genel API düzeyinde ISP'den yararlanır. Her modül, tek bir ortak cephe yerine tüketicileri için dar arayüzler dışa aktarır. Bu, işlevselliğinin yalnızca bir kısmını kullanan tüketicileri etkilemeden modülün iç uygulamasının değiştirilmesine olanak tanır.
Clean Architecture ile Android projelerinde, ISP UseCase'lere uygulanır: her UseCase, tek bir invoke veya execute yöntemiyle ayrı bir arayüzdür. İstemci (ViewModel), tüm bir depo yerine yalnızca ihtiyaç duyduğu UseCase'e bağımlıdır. Bu, bağımlılıkları şeffaf ve test edilebilir hale getirir.
Sıkça Sorulan Sorular
Evet, aşırı bölme mümkündür. ISP her yöntem için bir arayüz gerektirmez. Kriter: arayüz yöntemlerinin yalnızca bir kısmına ihtiyaç duyan bir istemci var mı? Tüm istemciler tüm yöntemleri kullanıyorsa, arayüzün bölünmesine gerek yoktur. Optimum bölme düzeyi gerçek kullanım senaryolarına göre belirlenir.
Parametre düzeyinde ISP şu anlama gelir: bir işlev, yalnızca bir kısmını kullanıyorsa çok sayıda alanı olan nesneleri kabul etmemelidir. Bunun yerine, yalnızca gerekli veriler iletilmeli veya uzmanlaşmış arayüzler kullanılmalıdır (örneğin, tam bir User yerine Renderable arayüzü).
LSP doğru kalıtım ve davranışsal alt tür uyumluluğuyla ilgilidir. ISP arayüz tasarımıyla ilgilidir: istemciler kullanmadıkları yöntemlere bağımlı olmamalıdır. LSP, “bir alt sınıf, temel sınıf yerine kullanılabilir mi?” sorusunu yanıtlar, ISP ise “istemcinin arayüzün tamamına ihtiyacı var mı?” sorusunu yanıtlar.
Dar arayüzler, mock nesneleri oluşturmayı basitleştirir: test, bir düzine yerine bir veya iki yöntemle bir mock oluşturur. Bir arayüzde ne kadar az yöntem varsa, davranışını taklit etmek o kadar kolaydır. Bu, test geliştiricisinin bilişsel yükünü azaltır ve mock mantığında hata olasılığını düşürür.
Arayüz kararlıysa ve tüm istemciler tüm yöntemleri kullanıyorsa, bölme gereksizdir. Tipik bir örnek: Apple tarafından tasarlanan UIKit protokolleri. Bunları bölmek risklidir çünkü UIKit, temsilcinin tam uygulamasını bekler. Bu gibi durumlarda, ISP ihlali API kararlılığıyla haklı çıkarılır.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun