ISP (Interface Segregation Principle) — SOLID-in dördüncü prinsibidir və bildirir: müştərilər istifadə etmədikləri metodlardan asılı olmamalıdır. Prinsip Robert Martin tərəfindən obyekt-yönümlü sistemlər üçün interfeyslərin layihələndirilməsi kontekstində formullaşdırılmışdır. Clean Architecture (2017) kitabında təsvir edildiyi kimi, interfeyslərin ayrılması prinsipi bir universal interfeys əvəzinə dar ixtisaslaşdırılmış interfeyslər yaratmağı tələb edir ki, bu da bağlılığı azaldır və dəyişikliklərin edilməsini asanlaşdırır.
Əsas Məqamlar
ISP (Interface Segregation Principle) — bütün müştərilər tərəfindən istifadə edilməyən metodları olan “qalın” interfeyslər yaratmağı qadağan edən interfeyslərin ayrılması prinsipidir. Onlarla metodu olan bir interfeys əvəzinə, hər biri öz müştəri qrupu üçün bir neçə kiçik interfeys layihələndirilir.
Prinsip Robert Martin tərəfindən “interfeyslərin çirklənməsi” probleminin həlli kimi təqdim edilmişdir, bir sinif ümumi interfeysdə elan edildiyi üçün ona lazım olmayan metodları tətbiq etməyə məcbur olduqda. Statik tipli dillərdə bu, boş tətbiqlərə və ya istisnaların atılmasına gətirib çıxarır — ISP-nin pozulmasının birbaşa əlaməti.
ISP və SRP bir-birini tamamlayır: SRP sinifin məsuliyyəti haqqında, ISP interfeyslərin müqavilələri haqqındadır. SRP deyir “bir sinif — dəyişiklik üçün bir səbəb”, ISP deyir “bir interfeys — bir müştəri ssenarisi”. Birlikdə sistemin hər bir elementinin dəqiq sərhədləri olan modul arxitekturasını formalaşdırırlar.
Fat Interface — konkret bir müştərinin ehtiyacından daha çox metod ehtiva edən interfeys. Məsələn, work, eat, sleep metodları olan Worker interfeysi. Robot işçi eat və sleep-i tətbiq etməməlidir, amma məcburdur. Həll yolu — Workable, Eatable, Sleepable-ə bölmək. Hər bir müştəri ehtiyacı olanı dəqiq alır.
Mobil inkişafda qalın interfeyslər nümayəndə və DataSource protokollarında rast gəlinir. Bir protokol iki fərqli ssenari (redaktə + göstərmə) üçün metodlar ehtiva edə bilər, halbuki konkret ekran onlardan yalnız birini istifadə edir.
ISP-nin tətbiqi hər bir interfeysin müştərilərinin təhlili ilə başlayır. Əgər iki müştəri bir interfeysin fərqli metod dəstlərindən istifadə edirsə — interfeys bölünməlidir. Hər bir yeni interfeys bir ssenari çərçivəsində birlikdə çağırılan metodları qruplaşdırır.
Bölmə mexanizmi: orijinal interfeys bir neçə dar interfeysə bölünür, hər biri ümumi hissəni (varsa) miras alır. Müştərilər ümumi interfeys əvəzinə lazım olan dar interfeysdən asılılığa keçirlər. Orijinal interfeysi tətbiq edən siniflər indi yalnız həqiqətən ehtiyac duyduqları dar interfeysləri tətbiq edirlər.
Əhəmiyyətli qeyd: bölünmə dərəcəsi müştərilərin sayı və onların ssenariləri ilə müəyyən edilir. ISP maksimum bölünmə tələb etmir (hər metod üçün mikrointerfeyslər). Bu, həddindən artıq mürəkkəbliyə gətirib çıxarardı. Məqsəd — müştərilərin lazımsız metodlardan asılılığını aradan qaldırmaq, hər bir interfeysin ölçüsünü minimuma endirmək deyil.
Əsas əlamətlər ISP-nin pozulmasına daxildir: siniflərin boş metodlarla interfeysi tətbiq etməsi (uydurma tətbiq), tətbiqlərdə UnsupportedOperationException istisnasının atılması, müştərilərin bir hissəsi tərəfindən istifadə edilməyən çox sayda parametr və ya qaytarılan tiplər, və müştərilərin yalnız bir hissəsinə təsir edən tez-tez interfeys dəyişiklikləri.
Android inkişafında ISP-nin pozulmasının tipik nümunəsi — klik, uzun klik və sürüşdürmə üçün metodları ehtiva edən OnItemClickListener interfeysi. Konkret ekran yalnız klikdən istifadə edərsə — qalan metodlar boş qalır. Həll — OnItemClickListener, OnItemLongClickListener, OnItemSwipeListener-ə bölmək.
iOS inkişafında ISP-nin pozulması UIKit nümayəndələrində özünü göstərir: bir protokol komponentin müxtəlif vəziyyətləri üçün metodlar ehtiva edir. UITableViewDelegate göstərmə, seçmə, redaktə və sürüşdürmə hərəkətləri üçün metodları ehtiva edir. Çox vaxt tərtibatçılar onlarla boş metodla bütün protokolu tətbiq edirlər. Məsuliyyət qruplarına görə bir neçə protokola bölmək problemi həll edir.
Problem təkcə kodun estetikasında deyil. İnterfeys dəyişdikdə (yeni metod əlavə olunduqda), bütün tətbiq edən siniflər yenilənməlidir — hətta yeni metoda ehtiyacı olmayanlar da. Onlarla ekranı olan mobil inkişafda bu, kaskad dəyişikliklərə gətirib çıxarır. ISP hər bir müştərini ona aid olmayan dəyişikliklərdən təcrid edir.
Gizli ISP pozulması konfiqurasiya parametrləri vasitəsilə baş verir. Metod çox sayda sahəsi olan bir obyekt qəbul edərsə, müştəri isə onlardan yalnız 2-3-nü istifadə edərsə — bu bölünmə üçün siqnaldır. Alternativ: minimal parametr dəsti olan bir neçə ixtisaslaşdırılmış metod.
Android inkişafında ISP, tətbiqin bütün parametrlərini oxumaq və yazmaq üçün bir SharedPreferencesManager istifadə edərkən pozulur. Yalnız temanı oxumağa ehtiyacı olan Fragment, müxtəlif məlumat növləri üçün onlarla metodu olan qlobal menecerdən asılılıq alır. ThemePreferenceProvider, AuthPreferenceProvider, FeatureFlagProvider-ə bölünmə — konfiqurasiya xidmətləri səviyyəsində ISP-nin tətbiqidir. Hər bir təminatçı müştərilərinə lazım olan dəqiq metodları ehtiva edir.
Məlumatlarla işləmək üçün interfeysi olan Android nümunəsini nəzərdən keçirək. ISP-nin pozulması — bütün CRUD əməliyyatları üçün bir interfeys, halbuki bütün müştərilərə bütün əməliyyatlar lazım deyil.
// ISP-nin pozulması: qalın interfeys
interface UserRepository {
fun getAll(): List<User>
fun getById(id: Int): User
fun save(user: User)
fun delete(id: Int)
}
// ISP tətbiq edildikdən sonra: dar interfeyslər
interface UserReader {
fun getAll(): List<User>
fun getById(id: Int): User
}
interface UserWriter {
fun save(user: User)
fun delete(id: Int)
}
// ReadOnlyViewModel yazma metodlarından asılı deyil
class ReadOnlyViewModel(
private val reader: UserReader
)
Media ilə işləmək üçün protokolların bölünməsi ilə iOS nümunəsi:
// ISP-nin pozulması: media ilə iş üçün bir protokol
protocol MediaService {
func play(url: URL)
func pause()
func stop()
func upload(data: Data) async -> URL
func download(url: URL) async -> Data
}
// ISP-dən sonra: məsuliyyətə görə protokollara bölmə
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 yükləmə metodlarından asılı deyil
class PlayerViewModel {
private let player: MediaPlayer
}
Praktiki nəticə: ISP müştəriləri interfeysin onlardan asılı olmayan hissələrindəki dəyişikliklərdən qoruyur. UserRepository-nin UserReader və UserWriter-ə bölünməsi o deməkdir ki, save-dəki dəyişikliklər ReadOnlyViewModel-ə təsir etmir və əksinə. Hər bir müştəri istifadə etmədiyi funksionallıqdan təcrid olunur və sistemin digər hissələri təkmilləşdirilərkən dəyişiklik tələb etmir.
ISP və SRP — təbii cütlük. SRP sinfin dəyişiklik üçün bir səbəbi olması lazım olduğunu müəyyən edir. ISP eyni məntiqi interfeyslərə tətbiq edir: interfeys bir müştəri ssenarisinə xidmət etməlidir. Sinif bir neçə dar interfeysi tətbiq edə bilər (hər biri bir məsuliyyətə uyğundur), bu, bir neçə məsuliyyəti olan bir qalın interfeysdən daha təmizdir.
ISP və OCP də əlaqəlidir: dar interfeysləri genişləndirmək daha asandır. Dar interfeysə yeni metod əlavə etmək yalnız onun müştərilərinə təsir edir. Qalın interfeysə metod əlavə etmək bütün müştərilərə təsir edir — potensial olaraq OCP-ni pozur, əgər müştərilər öz tətbiqlərini dəyişməyə məcbur olurlarsa.
ISP və DIP birlikdə işləyir: DIP abstraksiyalardan asılılıq tələb edir. ISP bu abstraksiyaları dar və fokuslu edir. Geniş interfeysdən asılılıq hələ də abstraksiyadan asılılıqdır, lakin ISP baxımından “pis” abstraksiyadır. Dörd prinsip (SRP, OCP, ISP, DIP) “modulluq piramidasını” formalaşdırır: SRP və ISP sərhədləri, OCP və DIP genişləndirmə və birləşdirmə yollarını müəyyən edir.
Komponent arxitekturası mobil layihələrdə (modullar, xüsusiyyətlər, təbəqələr) ictimai API səviyyəsində ISP-dən faydalanır. Hər bir modul istehlakçıları üçün bir ümumi fasad deyil, dar interfeyslər ixrac edir. Bu, modulun daxili tətbiqini, onun funksionallığının yalnız bir hissəsini istifadə edən istehlakçılara təsir etmədən dəyişməyə imkan verir.
Təmiz Arxitektura ilə Android layihələrində ISP UseCase-ə tətbiq olunur: hər UseCase bir invoke və ya execute metodu olan ayrıca interfeysdir. Müştəri (ViewModel) yalnız ehtiyac duyduğu UseCase-dən asılıdır, bütün repozitoridən deyil. Bu, asılılıqları şəffaf və test edilə bilən edir.
Tez-tez verilən suallar
Bəli, həddindən artıq bölünmə mümkündür. ISP hər metod üçün bir interfeys tələb etmir. Meyar: interfeysin yalnız bir hissəsinə ehtiyacı olan bir müştəri varmı? Əgər bütün müştərilər bütün metodlardan istifadə edirsə — interfeysi bölmək lazım deyil. Optimal bölünmə səviyyəsi real istifadə ssenariləri ilə müəyyən edilir.
Parametr səviyyəsində ISP o deməkdir: funksiya çox sayda sahəsi olan obyektləri qəbul etməməlidir, əgər onların yalnız bir hissəsini istifadə edirsə. Bunun əvəzinə yalnız lazımi məlumatları ötürmək və ya ixtisaslaşdırılmış interfeyslərdən istifadə etmək lazımdır (məsələn, tam User əvəzinə Renderable interfeysi).
LSP düzgün miras və alt tiplərin davranış uyğunluğu haqqındadır. ISP interfeyslərin layihələndirilməsi haqqındadır: müştərilər istifadə etmədikləri metodlardan asılı olmamalıdır. LSP “alt sinif əsas sinif əvəzinə istifadə edilə bilərmi?” sualına cavab verir, ISP — “müştəriyə bütün interfeys lazımdırmı?”.
Dar interfeyslər mock-obyektlərin yaradılmasını asanlaşdırır: test bir-iki metodu olan mock yaradır, onlarla metodla deyil. İnterfeysdə nə qədər az metod varsa, onun davranışını simulyasiya etmək bir o qədər asandır. Bu, test tərtibatçısının idrak yükünü azaldır və mock məntiqində səhv ehtimalını azaldır.
Əgər interfeys sabitdir və bütün müştərilər bütün metodlardan istifadə edirsə — bölünmə artıqdır. Tipik nümunə: Apple tərəfindən layihələndirilmiş UIKit protokolları. Onları bölmək risklidir, çünki UIKit nümayəndənin tam tətbiqini gözləyir. Belə hallarda ISP-nin pozulması API-nin sabitliyi ilə əsaslandırılır.
Xülasə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun