Esiste un principio di interfaccia "chiedi solo quello che ti serve"?


9

Sono cresciuto nell'uso di un principio per la progettazione e il consumo di interfacce che dice sostanzialmente "chiedi solo ciò di cui hai bisogno".

Ad esempio, se ho un sacco di tipi che possono essere eliminati, creerò Deletableun'interfaccia:

interface Deletable {
   void delete();
}

Quindi posso scrivere una classe generica:

class Deleter<T extends Deletable> {
   void delete(T t) {
      t.delete();
   }
}

Altrove nel codice chiederò sempre la minima responsabilità possibile per soddisfare le esigenze del codice client. Quindi, se ho solo bisogno di eliminare un File, chiederò ancora un Deletable, non un File.

Questo principio è conoscenza comune e ha già un nome accettato? È controverso? È discusso nei libri di testo?


1
Accoppiamento allentato forse? O interfacce strette?
martedì

Risposte:


16

Credo che ciò si riferisca a ciò che Robert Martin chiama il principio di segregazione dell'interfaccia . Le interfacce sono divise in piccole e concise in modo che i consumatori (clienti) debbano solo conoscere i metodi che li interessano. Puoi scoprire di più su SOLID .


4

Per espandere l'ottima risposta di Vadim, risponderò alla domanda "è controversa" con "no, non proprio".

In generale, la segregazione delle interfacce è una buona cosa, riducendo il numero complessivo di "motivi per cambiare" dei vari oggetti coinvolti. Il principio fondamentale è che quando un'interfaccia con più metodi deve essere modificata, si supponga di aggiungere un parametro a uno dei metodi dell'interfaccia, quindi tutti i consumatori dell'interfaccia devono almeno essere ricompilati, anche se non hanno utilizzato il metodo che è cambiato. "Ma è solo una ricompilazione!", Ti sento dire; questo può essere vero, ma tieni presente che in genere, tutto ciò che ricompili deve essere espulso come parte di una patch software, non importa quanto significativa sia la modifica al binario. Queste regole sono state originariamente concettualizzate nei primi anni '90, quando la workstation desktop media era meno potente del telefono in tasca, il dial-up da 14,4k baud era blazin 'e 3,5 "floppy" da 1,44 MB erano i principali supporti rimovibili. Anche nell'attuale era del 3G / 4G, gli utenti di Internet wireless hanno spesso piani di dati con limiti, quindi quando si rilascia un aggiornamento, meno binari devono essere scaricati, meglio è.

Tuttavia, come tutte le buone idee, la segregazione delle interfacce può andare male se implementata in modo errato. Prima di tutto, c'è la possibilità che segregando le interfacce mantenendo l'oggetto che implementa quelle interfacce (soddisfacendo le dipendenze) relativamente invariato, si potrebbe finire con un "Hydra", un parente dell'antimodello "God Object" dove la natura onnisciente e onnipotente dell'oggetto è nascosta alla dipendenza dalle interfacce strette. Ti ritroverai con un mostro a molte teste che è difficile da mantenere almeno quanto lo sarebbe l'Oggetto di Dio, oltre al sovraccarico di mantenere tutte le sue interfacce. Non esiste un numero rigido di interfacce che non dovresti superare, ma ogni interfaccia implementata su un singolo oggetto deve essere preceduta rispondendo alla domanda "Questa interfaccia contribuisce all'oggetto"

In secondo luogo, potrebbe non essere necessaria un'interfaccia per metodo, nonostante ciò che SRP può dirti. Potresti finire con "ravioli code"; così tanti pezzi di dimensioni ridotte che è difficile rintracciare per scoprire esattamente dove le cose accadono realmente. Non è inoltre necessario dividere un'interfaccia con due metodi se tutti gli utenti attuali dell'interfaccia necessitano di entrambi i metodi. Anche se una delle classi dipendenti necessita solo di uno dei due metodi, è generalmente accettabile non dividere l'interfaccia se i suoi metodi concettualmente hanno una coesione molto elevata (buoni esempi sono i "metodi antonimici" che sono esattamente opposti l'uno rispetto all'altro).

La segregazione dell'interfaccia dovrebbe essere basata sulle classi che dipendono dall'interfaccia:

  • Se esiste solo una classe dipendente dall'interfaccia, non separare. Se la classe non utilizza uno o più dei metodi dell'interfaccia ed è l'unico consumatore dell'interfaccia, è probabile che non dovresti aver esposto questi metodi in primo luogo.

  • Se esiste più di una classe dipendente dall'interfaccia e tutti i dipendenti utilizzano tutti i metodi dell'interfaccia, non separare; se è necessario modificare l'interfaccia (per aggiungere un metodo o modificare una firma), tutti i consumatori attuali saranno interessati dalla modifica indipendentemente dal fatto che si separi o meno (anche se si sta aggiungendo un metodo che almeno un dipendente non avrà bisogno, considerare attentamente se la modifica deve invece essere implementata come una nuova interfaccia, possibilmente ereditando da quella esistente).

  • Se esiste più di una classe dipendente dall'interfaccia e non usano tutti gli stessi metodi, è un candidato per la segregazione. Guarda la "coerenza" dell'interfaccia; tutti i metodi promuovono un unico obiettivo di programmazione molto specifico? Se riesci a identificare più di uno scopo principale dell'interfaccia (e dei suoi implementatori), considera di dividere le interfacce lungo quelle linee per creare interfacce più piccole con meno "motivi per cambiare".


Vale anche la pena notare che la segregazione dell'interfaccia può essere buona e dandy se si utilizza un linguaggio / sistema OOP che può consentire al codice di specificare una combinazione precisa di interfacce, ma almeno in .NET possono causare alcuni forti mal di testa, dal momento che non c'è decente modo di specificare una raccolta di "cose ​​che implementano IFoo e IBar, ma che altrimenti non avrebbero nulla in comune".
supercat

I parametri di tipo generico possono essere definiti con criteri che includono l'implementazione di più interfacce, ma hai ragione nel dire che le espressioni che richiedono un tipo statico in genere non possono supportare la specifica di più di una. Se è necessario che un tipo statico implementi sia IFoo che IBar e controlli entrambe queste interfacce, potrebbe essere una buona idea implementare IBaz : IFoo, IBare richiederlo invece.
KeithS

Se il codice client potrebbe aver bisogno di qualcosa che può essere usato come IFooe IBar, definire un composito IFooBarpotrebbe essere una buona idea, ma se le interfacce sono suddivise in modo fine, è facile finire per richiedere dozzine di tipi di interfaccia distinti. Considera le seguenti raccolte di funzionalità: Enumera, conta conteggi, Leggi ennesimo elemento, Scrivi ennesimo elemento, Inserisci prima di ennesimo elemento, Elimina ennesimo elemento, Nuovo elemento (ingrandisci raccolta e restituisci indice di nuovo spazio) e Aggiungi. Nove metodi: ECRWIDNA. Potrei probabilmente descrivere dozzine di tipi che supporterebbero naturalmente molte combinazioni diverse.
supercat,

Le matrici, ad esempio, supporterebbero ECRW. Un arraylist sosterrebbe ECRWIDNA. Un elenco thread-safe potrebbe supportare ECRWNA [sebbene A sia generalmente utile solo per precompilare l'elenco]. Un wrapper di array di sola lettura potrebbe supportare ECR. Un'interfaccia di elenco covariante potrebbe supportare ECRD. Un'interfaccia non generica potrebbe fornire supporto C o CD di tipo sicuro. Se Swap fosse un'opzione, alcuni tipi potrebbero supportare CS ma non D (ad es. Array) mentre altri supporterebbero CDS. Cercare di definire tipi di interfaccia distinti per ogni combinazione necessaria di abilità sarebbe un incubo.
supercat,

Ora immagina di volere la possibilità di avvolgere una raccolta con un oggetto che può fare tutto ciò che la raccolta può fare, ma che registra ogni transazione. Di quanti involucri sarebbe necessario? Se tutte le raccolte ereditate da un'interfaccia comune che includesse proprietà per identificare le loro abilità, sarebbe sufficiente un wrapper. Se tutte le interfacce sono distinte, tuttavia, ne occorrerebbero dozzine.
supercat,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.