Il principio di segregazione dell'interfaccia afferma:
Nessun client dovrebbe essere costretto a dipendere da metodi che non utilizza. L'ISP suddivide le interfacce che sono molto grandi in quelle più piccole e più specifiche in modo che i clienti debbano solo conoscere i metodi che li interessano.
Ci sono alcune domande senza risposta qui. Uno è:
Quanto piccolo?
Tu dici:
Attualmente mi occupo di questo dividendo lo spazio dei nomi del modulo in base alle esigenze dei suoi clienti.
Chiamo questo manuale digitando anatra . Costruisci interfacce che espongono solo le esigenze di un client. Il principio di segregazione dell'interfaccia non è semplicemente la tipizzazione manuale delle anatre.
Ma l'ISP non è semplicemente una richiesta di interfacce di ruoli "coerenti" che possono essere riutilizzate. Nessun progetto di interfaccia di ruolo "coerente" può difendersi perfettamente dall'aggiunta di un nuovo client con le proprie esigenze di ruolo.
ISP è un modo per isolare i clienti dall'impatto delle modifiche al servizio. È stato progettato per rendere la compilazione più veloce mentre si apportano modifiche. Sicuramente ha altri vantaggi, come non rompere i clienti, ma quello era il punto principale. Se sto cambiando la count()
firma della funzione servizi è bello se i client che non usano count()
non hanno bisogno di essere modificati e ricompilati.
Questo è il motivo per cui mi interessa il principio di segregazione dell'interfaccia. Non è qualcosa che credo nella fede così importante. Risolve un vero problema.
Quindi il modo in cui dovrebbe essere applicato dovrebbe risolvere un problema per te. Non esiste un modo di morte cerebrale per applicare l'ISP che non può essere sconfitto con il giusto esempio di un cambiamento necessario. Dovresti guardare come sta cambiando il sistema e fare delle scelte che permetteranno di calmare le cose. Esploriamo le opzioni.
Prima chiediti: in questo momento è difficile apportare modifiche all'interfaccia di servizio? Altrimenti, esci e gioca fino a quando non ti calmi. Questo non è un esercizio intellettuale. Assicurati che la cura non sia peggiore della malattia.
Se molti client utilizzano lo stesso sottoinsieme di funzioni, ciò richiede interfacce riutilizzabili "coerenti". Il sottoinsieme probabilmente si concentra su un'idea che possiamo pensare al ruolo che il servizio fornisce al cliente. È bello quando funziona. Questo non funziona sempre.
Se molti client utilizzano diversi sottogruppi di funzioni, è possibile che il client stia effettivamente utilizzando il servizio attraverso più ruoli. Va bene, ma rende i ruoli difficili da vedere. Trovali e prova a stuzzicarli. Ciò può riportarci nel caso 1. Il client utilizza semplicemente il servizio attraverso più di un'interfaccia. Per favore, non iniziare a trasmettere il servizio. Semmai ciò significherebbe passare il servizio nel client più di una volta. Funziona ma mi viene da chiedersi se il servizio non è una grossa palla di fango che deve essere rotta.
Se molti client usano sottoinsiemi diversi ma non vedete ruoli nemmeno permettendo che i client possano utilizzarne più di uno, allora non avete niente di meglio della digitazione duck per progettare le vostre interfacce. Questo modo di progettare le interfacce assicura che il client non sia esposto a nemmeno una funzione che non sta utilizzando ma garantisce quasi che l'aggiunta di un nuovo client comporterà sempre l'aggiunta di una nuova interfaccia che mentre l'implementazione del servizio non deve sapere su di esso sarà l'interfaccia che aggrega il ruolo interfacce. Abbiamo semplicemente scambiato un dolore per un altro.
Se molti client utilizzano diversi sottogruppi, si sovrappongono, si prevede che verranno aggiunti nuovi client che avranno bisogno di sottoinsiemi imprevedibili e non si è disposti a interrompere il servizio, quindi prendere in considerazione una soluzione più funzionale. Dal momento che le prime due opzioni non hanno funzionato e sei davvero in una brutta posizione in cui nulla sta seguendo uno schema e stanno arrivando più cambiamenti quindi considera di fornire a ciascuna funzione la propria interfaccia. Finire qui non significa che l'ISP non sia riuscito. Se qualcosa falliva, era il paradigma orientato agli oggetti. Le interfacce a metodo singolo seguono l'ISP all'estremo. È un bel po 'di digitazione della tastiera, ma potresti scoprire che improvvisamente rende le interfacce riutilizzabili. Ancora una volta, assicurati che non ci sia
Quindi si scopre che possono diventare davvero molto piccoli.
Ho preso questa domanda come una sfida per applicare l'ISP nei casi più estremi. Ma tieni presente che è meglio evitare gli estremi. In una progettazione ben ponderata che applica altri principi SOLID questi problemi di solito non si verificano o contano, quasi altrettanto.
Un'altra domanda senza risposta è:
Chi possiede queste interfacce?
Vedo ripetutamente interfacce progettate con quella che chiamo mentalità da "biblioteca". Siamo stati tutti colpevoli della codifica Monkey-See-Monkey-Do in cui stai facendo qualcosa perché è così che l'hai vista. Siamo colpevoli della stessa cosa con le interfacce.
Quando guardo un'interfaccia progettata per una lezione in una biblioteca pensavo: oh, questi ragazzi sono dei professionisti. Questo deve essere il modo giusto di fare un'interfaccia. Quello che non riuscivo a capire è che un limite della biblioteca ha i suoi bisogni e problemi. Per prima cosa, una biblioteca ignora completamente il design dei suoi clienti. Non tutti i confini sono uguali. E a volte anche lo stesso confine ha modi diversi di attraversarlo.
Ecco due semplici modi per esaminare il design dell'interfaccia:
Interfaccia di proprietà del servizio. Alcune persone progettano ogni interfaccia per esporre tutto ciò che un servizio può fare. Puoi anche trovare opzioni di refactoring negli IDE che scriveranno un'interfaccia per te usando qualunque classe la dai.
Interfaccia di proprietà del cliente. L'ISP sembra sostenere che questo è giusto e che il servizio di proprietà è sbagliato. Dovresti interrompere ogni interfaccia tenendo presente le esigenze dei clienti. Poiché il client possiede l'interfaccia, dovrebbe definirla.
Allora chi ha ragione?
Prendi in considerazione i plugin:
Chi possiede le interfacce qui? I clienti? I servizi?
Risulta entrambi.
I colori qui sono strati. Il livello rosso (a destra) non dovrebbe sapere nulla sul livello verde (a sinistra). Il livello verde può essere modificato o sostituito senza toccare il livello rosso. In questo modo qualsiasi livello verde può essere inserito nel livello rosso.
Mi piace sapere cosa dovrebbe sapere su cosa, e cosa non dovrebbe sapere. Per me, "cosa sa di cosa?", È la domanda architettonica più importante.
Cerchiamo di chiarire un po 'di vocabolario:
[Client] --> [Interface] <|-- [Service]
----- Flow ----- of ----- control ---->
Un client è qualcosa che usa.
Un servizio è qualcosa che viene utilizzato.
Interactor
sembra essere entrambi.
L'ISP afferma di interrompere le interfacce per i client. Bene, applichiamolo qui:
Presenter
(un servizio) non dovrebbe dettare Output Port <I>
all'interfaccia. L'interfaccia dovrebbe essere ristretta a ciò di cui Interactor
(qui agendo come cliente). Ciò significa che l'interfaccia CONOSCE riguardo Interactor
all'ISP e, per seguire l'ISP, deve cambiare con essa. E questo va bene.
Interactor
(qui agendo come un servizio) non dovrebbe dettare Input Port <I>
all'interfaccia. L'interfaccia dovrebbe essere ristretta a ciò di cui Controller
(un cliente) ha bisogno. Ciò significa che l'interfaccia CONOSCE riguardo Controller
all'ISP e, per seguire l'ISP, deve cambiare con essa. E questo non va bene.
Il secondo non va bene perché il livello rosso non dovrebbe conoscere il livello verde. Quindi l'ISP è sbagliato? Beh un pò. Nessun principio è assoluto. Questo è un caso in cui gli sciocchi a cui piace l'interfaccia per mostrare tutto ciò che il servizio può fare risultano essere giusti.
Almeno, hanno ragione se il Interactor
non fa nulla di diverso da questo caso d'uso necessario. Se lo Interactor
fa per altri casi d'uso, non c'è motivo che questo Input Port <I>
debba conoscerli. Non sono sicuro del perché Interactor
non possa concentrarsi solo su un caso d'uso, quindi questo non è un problema, ma succede qualcosa.
Ma l' input port <I>
interfaccia semplicemente non può essere asservita al Controller
client e avere questo come un vero plugin. Questo è un limite di "biblioteca". Un negozio di programmazione completamente diverso potrebbe scrivere il livello verde anni dopo la pubblicazione del livello rosso.
Se stai attraversando un limite di "libreria" e senti la necessità di applicare l'ISP anche se non possiedi l'interfaccia dall'altra parte, dovrai trovare un modo per restringere l'interfaccia senza cambiarla.
Un modo per farlo è un adattatore. Mettilo tra client come Controler
e l' Input Port <I>
interfaccia. L'adattatore accetta Interactor
come Input Port <I>
e delega il suo lavoro. Tuttavia, espone solo ciò di cui i clienti hanno Controller
bisogno attraverso un'interfaccia di ruolo o interfacce di proprietà del livello verde. L'adattatore non segue l'ISP da solo, ma consente a classi più complesse come Controller
godersi l'ISP. Ciò è utile se ci sono meno adattatori rispetto a client come quelli Controller
che li usano e quando ti trovi in una situazione insolita in cui stai attraversando un limite della libreria e, nonostante sia pubblicata, la libreria non smetterà di cambiare. Ti guardo Firefox. Ora quei cambiamenti rompono solo i tuoi adattatori.
Che cosa significa questo? Significa onestamente che non mi hai fornito informazioni sufficienti per dirti cosa dovresti fare. Non so se non seguire l'ISP ti sta causando un problema. Non so se seguirlo non finirebbe per causarti ulteriori problemi.
So che stai cercando un semplice principio guida. L'ISP cerca di essere quello. Ma lascia molto non detto. Ci credo. Sì, per favore non forzare i clienti a dipendere da metodi che non usano, senza una buona ragione!
Se hai una buona ragione, come progettare qualcosa per accettare plug-in, fai attenzione ai problemi che non seguono le cause dell'ISP (è difficile cambiare senza rompere i client) e ai modi per mitigarli (mantieni Interactor
o almeno Input Port <I>
focalizzato su uno stabile caso d'uso).