Dividi grandi interfacce


9

Sto usando una grande interfaccia con circa 50 metodi per accedere a un database. L'interfaccia è stata scritta da un mio collega. Ne abbiamo discusso:

Io: 50 metodi sono troppi. È un odore di codice.
Collega: cosa devo fare al riguardo? Vuoi l'accesso al DB - ce l'hai.
Io: Sì, ma non è chiaro e difficilmente gestibile in futuro.
Collega: OK, hai ragione, non è carino. Come dovrebbe apparire l'interfaccia allora?
Io: che ne dici di 5 metodi che restituiscono oggetti che hanno, ad esempio, 10 metodi ciascuno?

Mmmh, ma non sarebbe lo stesso? Questo porta davvero a una maggiore chiarezza? Ne vale la pena?

Ogni tanto mi trovo in una situazione in cui voglio un'interfaccia e la prima cosa che mi viene in mente è un'unica, grande interfaccia. Esiste un modello di progettazione generale per questo?


Aggiornamento (rispondendo al commento di SJuan):

Il "tipo di metodi": è un'interfaccia per il recupero di dati da un database. Tutti i metodi hanno il modulo (pseudocodice)

List<Typename> createTablenameList()

I metodi e le tabelle non sono esattamente in una relazione 1-1, l'enfasi è più sul fatto che si ottiene sempre un qualche tipo di elenco che proviene da un database.


12
Mancano informazioni pertinenti (che tipo di metodi hai). Comunque, la mia ipotesi: se dividi solo per numero, allora il tuo collega ha ragione, non stai migliorando nulla. Un possibile criterio di divisione sarebbe "entità" (quasi l'equivalente di una tabella) restituito (quindi, a UserDaoe a CustomerDaoe a ProductDao)
SJuan76

In effetti alcune tabelle sono semanticamente vicine ad altre tabelle che formano "cricche". Quindi fai i metodi.
TobiMcNamobi,

È possibile vedere il codice? So che ha 4 anni e probabilmente l'hai risolto ormai: D Ma mi piacerebbe pensare a questo problema. Ho risolto qualcosa del genere prima.
clankill3r,

@ clankill3r In effetti non ho più accesso all'interfaccia specifica che mi ha fatto pubblicare la domanda sopra. Il problema è però più generale. Immagina un DB con circa 50 tabelle e per ogni tabella un metodo comeList<WeatherDataRecord> createWeatherDataTable() {db.open(); return db.select("*", "tbl_weatherData");}
TobiMcNamobi

Risposte:


16

Sì, 50 metodi è un odore di codice, ma un odore di codice significa dargli una seconda occhiata, non che sia automaticamente sbagliato. Se ogni client che utilizza quella classe necessita potenzialmente di tutti e 50 i metodi, potrebbe non esserci un caso per dividerlo. Tuttavia, questo è improbabile. Il punto è che dividere arbitrariamente un'interfaccia può essere peggio che non dividerla affatto.

Non esiste un unico modello per risolverlo, ma il principio che descrive lo stato desiderato è il Principio di segregazione dell'interfaccia (l'io in SOLID), che afferma che nessun client dovrebbe essere costretto a dipendere da metodi che non utilizza .

La descrizione dell'ISP ti dà un suggerimento su come risolverlo: guarda il client . Spesso, semplicemente guardando una classe, sembra che tutto vada insieme, ma emergono chiare divisioni quando si guardano i client che usano quella classe. Considerare sempre prima i client quando si progetta un'interfaccia.

L'altro modo per determinare se e dove un'interfaccia deve essere suddivisa è effettuare una seconda implementazione. Ciò che accade spesso è che la tua seconda implementazione non ha bisogno di molti metodi, quindi quelli chiaramente dovrebbero essere suddivisi nella loro stessa interfaccia.


Questa e la risposta di utnapistim sono davvero fantastiche.
TobiMcNamobi,

13

Collega: OK, hai ragione, non è carino. Come dovrebbe apparire l'interfaccia allora?

Io: che ne dici di 5 metodi che restituiscono oggetti che hanno, ad esempio, 10 metodi ciascuno?

Non sono buoni criteri (in realtà non ci sono affatto criteri in quella dichiarazione). Puoi raggrupparli per (supponendo che la tua applicazione sia un'app per le transazioni finanziarie, per i miei esempi):

  • funzionalità (inserimenti, aggiornamenti, selezioni, transazioni, metadati, schema, ecc.)
  • entità (utente DAO , deposito DAO, ecc.)
  • area di applicazione (transazioni finanziarie, gestione utenti, totali ecc.)
  • livello di astrazione (tutto il codice di accesso alla tabella è un modulo separato; tutte le API selezionate sono nella propria gerarchia, il supporto delle transazioni è separato, tutto il codice di conversione in un modulo, tutto il codice di convalida in un modulo, ecc.)

Mmmh, ma non sarebbe lo stesso? Questo porta davvero a una maggiore chiarezza? Ne vale la pena?

Se scegli i giusti criteri, sicuramente. In caso contrario, sicuramente no :).

Alcuni esempi:

  • guarda gli oggetti ADODB per un esempio semplicistico di primitive OO (l'API DB probabilmente lo offre già)

  • guarda il modello di dati di Django ( https://docs.djangoproject.com/en/dev/topics/db/models/ ) per un'idea del modello di dati con un alto livello di astrazione (in C ++ probabilmente avrai bisogno di un po 'più di piastra della caldaia codice, ma è una bella idea). Questa implementazione è progettata con un ruolo "modello" in mente, all'interno del modello di progettazione MVC.

  • guarda l'API sqlite per un'idea API piatta ( http://www.sqlite.org/c3ref/funclist.html ), composta solo da primitive funzionali (API C).


3

Ogni tanto mi trovo in una situazione in cui voglio un'interfaccia e la prima cosa che mi viene in mente è una grande interfaccia. Esiste un modello di progettazione generale per questo?

È un disegno anti-modello chiamato classe monolitica . Avere 50 metodi in una classe o interfaccia è una probabile violazione dell'SRP . La classe monolitica nasce perché cerca di essere tutto per tutti.

Il metodo DCI risolve il problema. In sostanza, le molte responsabilità di una classe potrebbero essere suddivise in ruoli (scaricati in altre classi) che sono rilevanti solo in determinati contesti. L'applicazione dei ruoli può essere ottenuta in vari modi, inclusi mixin o decoratori . Questo approccio mantiene le classi concentrate e snelle.

Che ne dici di 5 metodi che restituiscono oggetti che hanno, come, 10 metodi ciascuno?

Questo suggerisce un'istanza di tutti i ruoli quando l'oggetto stesso è istanziato. Ma perché istanziare ruoli che potrebbero non essere necessari? Invece, istanza un ruolo nel contesto in cui è effettivamente necessario.

Se scopri che il refactoring verso DCI non è ovvio, potresti seguire un modello di visitatori più semplice . Fornisce un vantaggio simile senza enfatizzare la creazione di contesti di casi d'uso.

EDIT: il mio pensiero su questo è cambiato un po '. Ho fornito una risposta alternativa.


1

Mi sembra che ogni altra risposta manchi il punto. Il punto è che un'interfaccia dovrebbe idealmente definire un blocco atomico di comportamento. Questo è l'Io in SOLIDO.

Una classe dovrebbe avere una responsabilità, ma ciò potrebbe comunque includere comportamenti multipli. Per attenersi a un tipico oggetto client di database, questo può offrire la piena funzionalità CRUD. Sarebbero quattro comportamenti: creare, leggere, aggiornare ed eliminare. In un mondo SOLIDO puro il client di database implementerebbe non IDatabaseClient ma unstead ICreator, IReader, IUpdater e IDeleter.

Ciò avrebbe una serie di vantaggi. Innanzitutto, leggendo la dichiarazione di classe si imparerebbe immediatamente molto sulla classe, le interfacce che implementa raccontano l'intera storia. In secondo luogo, se l'oggetto client dovesse essere passato come argomento, uno ora ha diverse opzioni utili. Potrebbe essere passato come IReader e si potrebbe essere sicuri che la persona in grado di leggere sarebbe solo. Diversi comportamenti potrebbero essere testati separatamente.

Quando si tratta di test, tuttavia, la pratica comune è semplicemente schiaffeggiare un'interfaccia su una classe che è una replica 1: 1 dell'interfaccia di classe completa. Se il test è tutto ciò che ti interessa, questo potrebbe essere un approccio valido. Ti permette di creare manichini abbastanza rapidamente. Ma non è quasi mai SOLIDO e in realtà è un abuso di interfacce per uno scopo dedicato.

Quindi sì, 50 metodi sono un odore ma dipende dall'intento e dallo scopo se è cattivo o no. Non è certamente l'ideale.


0

I livelli di accesso ai dati tendono ad avere molti metodi collegati a una classe. Se hai mai lavorato con Entity Framework o altri strumenti ORM, vedrai che generano centinaia di metodi. Presumo che tu e il tuo collega lo stiate implementando manualmente. Non è necessario un odore di codice, ma non è carino da guardare. Senza conoscere il tuo dominio, è difficile da dire.


Metodi o proprietà?
JeffO,

0

Uso i protocolli (chiamali interfacce se vuoi) quasi universalmente per tutte le API sia con FP che con OOP. (Ricorda la matrice? Non ci sono concrezioni!) Ci sono, naturalmente, tipi concreti, ma nell'ambito di un programma ogni tipo è pensato come qualcosa che svolge un ruolo in un certo contesto.

Ciò significa che gli oggetti passati attraverso i programmi, nelle funzioni, ecc. Possono essere considerati entità astratte con insiemi di comportamenti denominati. Si può pensare che l'oggetto abbia un ruolo che è un insieme di protocolli. Una persona (tipo concreto) potrebbe essere un uomo, un padre, un marito, un amico e un impiegato, ma non riesco a immaginare molte funzioni che considererebbero l'entità la somma di più di 2 di quelli.

Immagino sia possibile che un oggetto complesso possa rispettare una serie di protocolli diversi, ma sarebbe comunque difficile trovare un API a 50 metodi. La maggior parte dei protocolli ha 1 o 2 metodi, forse 3, ma mai 50! Qualsiasi entità che abbia 50 metodi dovrebbe essere aggregata a un gruppo di componenti più piccoli, ciascuno con le proprie responsabilità. L'entità in generale presenterebbe un'interfaccia più semplice che estrae la somma totale delle API all'interno di essa.

Invece di pensare in termini di oggetti e metodi, inizia a pensare in termini di astrazioni e contratti e quali ruoli svolge un soggetto in un determinato contesto.

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.