Quante sono troppe interfacce su una classe? [chiuso]

28

Probabilmente lo considererei un odore di codice o addirittura un anti-pattern per avere una classe che implementa 23 interfacce. Se è davvero un anti-modello, come lo chiameresti? O semplicemente non sta seguendo il principio di Responsabilità Singola?

    
posta Jonas Elfström 03.11.2011 - 13:54
fonte

10 risposte

36

Famiglia reale: non fanno niente di particolarmente pazzo, ma hanno un miliardo di titoli e sono in qualche modo legati alla maggior parte degli altri reali.

    
risposta data 03.11.2011 - 15:43
fonte
23

Sottopongo che questo anti-modello si chiamerà Jack of All Trades, o forse Too Many Hats.

    
risposta data 03.11.2011 - 14:10
fonte
17

Se dovessi dargli un nome, penso che lo chiamerei Hydra :

InGreekmythology,theLernaeanHydra(Greek:ΛερναίαὝδρα)wasanancientnamelessserpent-likechthonicwaterbeast,withreptiliantraits,(asitsnameevinces)thatpossessedmanyheads—thepoetsmentionmoreheadsthanthevase-painterscouldpaint,andforeachheadcutoffitgrewtwomore—andpoisonousbreathsovirulentevenhertracksweredeadly.

Diparticolareimportanzaèilfattochenonsolohamolteteste,macontinuaacresceresempredipiùedèimpossibiledauccidereacausadiesso.Questaèstatalamiaesperienzaconquestotipodidesign;glisviluppatoricontinuanoaintromettersisemprepiùinterfaccefinoaquandononnehatroppeperadattarsialloschermo,edaalloraèdiventatocosìprofondamenteradicatonellaprogettazionedelprogrammaenelleipotesichedividerloèunaprospettivasenzasperanza(seciprovi,inrealtàspessofinisconoperaverebisognodipiùinterfaccepercolmareildivario).

Unprimosegnalediimminenterovinaacausadiuna"Hydra" è il casting frequente di un'interfaccia all'altra, senza molta verifica della sanità, e spesso verso un obiettivo che non ha senso, come in:

public void CreateWidget(IPartLocator locator, int widgetTypeId)
{
    var partsNeeded = locator.GetPartsForWidget(widgetTypeId);
    return ((IAssembler)locator).BuildWidget(partsNeeded);
}

È ovvio quando si toglie dal contesto che c'è qualcosa di sospetto su questo codice, ma la probabilità che questo accada aumenta con le "teste" più un oggetto cresce, perché gli sviluppatori conoscono intuitivamente che sono " stai sempre con la stessa creatura.

Buona fortuna mai fare manutenzione su un oggetto che implementa 23 interfacce. Le probabilità che tu non causi danni collaterali nel processo sono ridotte a zero.

    
risposta data 10.11.2011 - 05:54
fonte
16

Mi viene in mente God Object ; un singolo oggetto che sa come fare TUTTO. Ciò deriva dalla scarsa aderenza ai requisiti di "coesione" delle due principali metodologie di progettazione; se si dispone di un oggetto con 23 interfacce, si dispone di un oggetto che sa come essere 23 cose diverse per i suoi utenti, e quelle 23 cose diverse non sono probabilmente sulla falsariga di una singola attività o area del sistema.

In SOLID, mentre il creatore di questo oggetto ha ovviamente cercato di seguire il principio di segregazione dell'interfaccia, hanno violato una regola precedente; Principio della singola responsabilità. C'è una ragione per cui è "SOLIDO"; S viene sempre al primo posto quando si pensa al design e seguono tutte le altre regole.

In GRASP, il creatore di una tale classe ha trascurato la regola della "alta coesione"; GRASP, a differenza di SOLID, insegna che un oggetto non ha una sola responsabilità, ma al massimo dovrebbe avere due o tre responsabilità strettamente correlate.

    
risposta data 03.11.2011 - 15:17
fonte
8

23 è solo un numero! Nello scenario più probabile, è abbastanza alto da allarmare. Tuttavia, se chiediamo, qual è il più alto numero di metodi / interfacce prima che possa ottenere che un tag venga chiamato come "anti-pattern"? È 5 o 10 o 25? Ti rendi conto che il numero non è davvero una risposta perché se 10 è buono, 11 può anche essere - e quindi qualsiasi numero intero dopo di ciò.

La vera domanda riguarda la complessità. E dovremmo sottolineare che il lungo codice, il numero di metodi o le grandi dimensioni della classe da qualsiasi misura è in realtà NON la definizione di complessità. Sì, un codice sempre più grande (un numero maggiore di metodi) lo rende difficile da leggere e comprendere per un nuovo principiante. Gestisce anche funzionalità potenzialmente diverse, un gran numero di eccezioni e algoritmi abbastanza evoluti per vari scenari. Ciò non significa che sia complesso - è solo difficile da digerire.

D'altra parte, un codice di dimensioni relativamente piccole che si può sperare di leggere in poche ore dentro e fuori- può essere ancora complesso. Ecco quando penso che il codice sia (inutilmente) complesso.

Ogni elemento di saggezza del design orientato agli oggetti può essere inserito qui per definire "complesso", ma resterei qui per mostrare quando "tanti metodi" sono un'indicazione di complessità.

  1. Conoscenza reciproca. (a.k.un accoppiamento) Molte volte in cui le cose sono scritte come classi, tutti pensiamo che sia un codice "bello" orientato agli oggetti. Ma l'assunzione dell'altra classe essenzialmente rompe il vero incapsulamento necessario. Quando si hanno metodi che "perdono" dettagli profondi sullo stato interno degli algoritmi - e l'applicazione è costruita con l'assunto chiave sullo stato interno della classe di servizio.

  2. Troppe ripetizioni (irruzione) Quando metodi che hanno nomi simili ma contraddistinguono il lavoro - o contraddicono nomi con funzionalità simili. Molte volte i codici si evolvono per supportare interfacce leggermente diverse per diverse applicazioni.

  3. Troppi ruoli Quando la classe continua ad aggiungere funzioni secondarie e continua ad espandersi di più come piace alla gente, solo per sapere che la classe in effetti è ora a due classi. Sorprendentemente, tutti iniziano con i requisiti genuini e nessun'altra classe esiste per farlo. Considera questo, esiste una Transazione di classe, che indica i dettagli della transazione. Sembra buono fino ad ora, ora qualcuno richiede la conversione del formato sul "tempo di transazione" (tra UTC e simili), in seguito le persone aggiungono regole per verificare se certe cose erano in certe date per convalidare le transazioni non valide. - Non scriverò l'intera storia, ma alla fine, la classe di transazione costruisce l'intero calendario con dentro e poi le persone iniziano a usare la parte "solo il calendario"! Questo è molto complesso (da immaginare) perché istanzerò "classe di transazione" per avere funzionalità che un "calendario" mi avrebbe fornito!

  4. (In) consistenza dell'API Quando faccio book_a_ticket () - Prenoto un biglietto! Questo è molto semplice a prescindere da quanti controlli e processi vengono fatti per realizzarlo. Ora diventa complesso quando inizia a scorrere il flusso del tempo. In genere si consentirebbe la "ricerca" e il possibile stato disponibile / non disponibile, quindi per ridurre il back -n-avanti si avvia il salvataggio di alcuni puntatori di contesto all'interno del ticket e quindi si riferiscono a prenotare il ticket. La ricerca non è l'unica funzionalità - le cose peggiorano dopo molte di queste "funzionalità collaterali". Nel processo il significato di book_a_ticket () implica book_that_ticket ()! e che può essere inimmaginabilmente complesso.

Probabilmente ci sono molte di queste situazioni che vedresti in un codice molto evoluto e sono sicuro che molte persone possono aggiungere scenari, dove "così tanti metodi" non ha senso o non fa ciò che ovviamente penseresti . Questo è l'anti-modello.

La mia esperienza personale è che quando i progetti che iniziano ragionevolmente dal basso verso l'alto, molte cose per le quali dovrebbero esserci state classi autonome da sole - rimane sepolto o peggio rimane ancora diviso tra classi diverse e aumenta l'accoppiamento . Molto spesso ciò che avrebbe meritato 10 classi, ma ne ha solo 4, è probabile che molti di essi abbiano un gran numero di metodi confusionari e numerosi. Chiamalo DIO e DRAGO, questo è ciò che è CATTIVO.

MA ti imbatti in classi MOLTO GRANDI che sono perfettamente coerenti, hanno 30 metodi e sono ancora molto PULITE. Possono essere buoni.

    
risposta data 10.11.2011 - 13:25
fonte
7

Le classi dovrebbero avere esattamente il giusto numero di interfacce; né più né meno.

Dire "troppi" sarebbe impossibile senza guardare se tutte quelle interfacce hanno un utile scopo in quella classe. Dichiarare che un oggetto implementa un'interfaccia significa che devi implementare i suoi metodi se ti aspetti che la classe compili. (Presumo che la classe che stai guardando lo faccia.) Se tutto nell'interfaccia è implementato e quelle implementazioni fanno qualcosa relativamente alle viscere della classe, sarebbe difficile dire che l'implementazione non dovrebbe esserci. L'unico caso in cui riesco a pensare a dove un'interfaccia che soddisfi quei criteri non ci sarebbe, è esterna: quando nessuno la usa.

Alcune lingue consentono di "sottocludere" le interfacce usando un meccanismo come la parola chiave extends di Java, e chiunque l'abbia scritta potrebbe non saperlo. Potrebbe anche darsi che tutti e 23 siano sufficientemente distanti da non aver senso aggregarli.

    
risposta data 03.11.2011 - 19:13
fonte
1

Suona come se avessero una coppia "getter" / "setter" per ogni proprietà, come è normale per un tipico "bean" e ha promosso tutti questi metodi all'interfaccia. Che ne dici di chiamarlo " ha bean ". O più "flatulenza" rabelaisiana dopo gli effetti ben noti di troppi fagioli.

    
risposta data 10.11.2011 - 04:17
fonte
1

Il numero di interfacce cresce spesso quando un oggetto generico viene utilizzato in ambienti diversi. In C # le varianti di IComparable, IEqualityComparer e IComparer consentono l'ordinamento in configurazioni distinte, quindi si potrebbe finire per implementarle tutte, alcune forse più di una volta poiché è possibile implementare le versioni generiche strongmente tipizzate e le versioni non generiche , inoltre puoi implementare più di uno dei generici.

Facciamo uno scenario di esempio, diciamo un webshop in cui è possibile acquistare crediti con cui è possibile acquistare qualcos'altro (i siti di foto d'archivio usano spesso questo schema). Potresti avere una classe "Valuta" e una classe "Crediti" che ereditano dalla stessa base. Valuta ha alcuni sovraccarichi dell'operatore e procedure di confronto che ti permettono di fare calcoli senza preoccuparti della proprietà "Valuta" (aggiungendo sterline ai dollari, ad esempio). I crediti sono più semplici ma hanno altri comportamenti distinti. Volendo essere in grado di confrontarli tra loro, si potrebbe finire per implementare IComparable e IComparable e le altre varianti di interfacce di confronto su entrambi (anche se usano un'implementazione comune sia che si trovi nella classe base o altrove).

Quando si implementa la serializzazione, ISerializable, IDeserializationCallback viene implementato. Quindi implementando stack undo-redo: viene aggiunto IClonable. Funzionalità IsDirty: IObservable, INotifyPropertyChanged. Permettere agli utenti di modificare i valori usando stringhe: IConvertable ... L'elenco può andare avanti e avanti ...

Nelle lingue moderne vediamo una tendenza diversa che aiuta a separare questi aspetti e li colloca nelle loro classi, al di fuori della classe principale. La classe o l'aspetto esterno viene quindi associato alla classe di destinazione utilizzando l'annotazione (attributi). Spesso è possibile rendere le classi di aspetto esterne più o meno generiche.

L'uso degli attributi (annotazione) è soggetto a riflessione. Uno svantaggio è la perdita di prestazioni (iniziale) minore. Uno svantaggio (spesso emotivo) è che i principi come l'incapsulamento devono essere rilassati.

Ci sono sempre altre soluzioni, ma per ogni soluzione ingegnosa c'è un compromesso o una presa. Ad esempio, l'utilizzo di una soluzione ORM potrebbe richiedere che tutte le proprietà vengano dichiarate virtuali. Le soluzioni di serializzazione potrebbero richiedere costruttori predefiniti sulle tue classi. Se stai utilizzando l'integrazione delle dipendenze potresti finire per implementare 23 interfacce su una singola classe.

Ai miei occhi, 23 interfacce non devono essere cattive per definizione. Potrebbe esserci uno schema ben congegnato, o qualche determinazione di principio come evitare l'uso della riflessione o credenze di incapsulamento estremo.

Ogni volta che si cambia un lavoro o si deve costruire su un'architettura esistente. Il mio consiglio è di familiarizzare prima di tutto, non cercare di refactoring tutto troppo velocemente. Ascolta lo sviluppatore originale (se è ancora lì) e cerca di capire i pensieri e le idee dietro ciò che vedi. Quando fai domande, fallo non per scomposizione, ma per imparare ... Sì, ognuno ha i propri martelli d'oro, ma più martelli puoi raccogliere più facilmente andrai d'accordo con i tuoi colleghi.

    
risposta data 15.11.2011 - 20:32
fonte
0

"Troppi" è soggettivo: stile di programmazione? prestazione? conformità agli standard? precedenti? un semplice senso di conforto / confidenza?

Finché il codice si comporta correttamente e non presenta problemi di manutenibilità, 23 potrebbe persino essere la nuova norma. Un giorno potrei quindi dire: "Puoi fare un eccellente lavoro con 23 interfacce, vedi: Jonas Elfström".

    
risposta data 15.11.2011 - 11:24
fonte
0

Direi che il limite dovrebbe essere un numero inferiore a 23 - più come 5 o 7,
tuttavia, questo non include alcun numero di interfacce ereditate da quelle interfacce o qualsiasi numero di interfacce implementate dalle classi di base.

(Quindi, N + qualsiasi numero di interfacce ereditate, dove N < = 7.)

Se la tua classe implementa troppe interfacce, probabilmente è una classe di Dio .

    
risposta data 15.11.2011 - 12:37
fonte

Leggi altre domande sui tag