Accoppiamento libero nel design orientato agli oggetti


16

Sto cercando di imparare GRASP e ho trovato questo spiegato ( qui a pagina 3 ) sull'accoppiamento basso e sono rimasto molto sorpreso quando ho trovato questo:

Considera il metodo addTrackper una Albumclasse, due possibili metodi sono:

addTrack( Track t )

e

addTrack( int no, String title, double duration )

Quale metodo riduce l'accoppiamento? Il secondo sì, poiché la classe che utilizza la classe Album non deve conoscere una classe Track. In generale, i parametri dei metodi dovrebbero usare tipi di base (int, char ...) e classi dai pacchetti java. *.

Tendo a diasgree con questo; Credo che addTrack(Track t)sia meglio che addTrack(int no, String title, double duration)per vari motivi:

  1. È sempre meglio per un metodo il minor numero possibile di parametri (secondo il Clean Code di Zio Bob nessuno o uno preferibilmente, 2 in alcuni casi e 3 in casi speciali; più di 3 necessita di refactoring - queste sono ovviamente raccomandazioni non regole di agrifoglio) .

  2. Se addTrackè un metodo di un'interfaccia e i requisiti richiedono che a Trackabbia più informazioni (ad esempio anno o genere), allora l'interfaccia deve essere modificata e in modo che il metodo supporti un altro parametro.

  3. L'incapsulamento è rotto; se si addTracktrova in un'interfaccia, non dovrebbe conoscere gli interni di Track.

  4. In realtà è più accoppiato nel secondo modo, con molti parametri. Supponiamo che il noparametro debba essere modificato da inta longperché ci sono più di MAX_INTtracce (o per qualsiasi motivo); allora sia il Trackmetodo che il metodo devono essere cambiati mentre se il metodo fosse addTrack(Track track)solo il Tracksarebbe cambiato.

Tutti e 4 gli argomenti sono in realtà collegati tra loro e alcuni di essi sono conseguenze di altri.

Quale approccio è migliore?


2
È un documento che è stato messo insieme da un professore o un formatore? Sulla base dell'URL del link fornito, sembra che fosse per una classe, anche se non vedo alcun credito nel documento su chi lo ha creato. Se facesse parte di una lezione, ti suggerirei di porre queste domande alla persona che ha fornito il documento. Sono d'accordo con il tuo ragionamento, comunque - mi sembrerebbe evidente che una classe Album vorrebbe conoscere intrinsecamente una classe Track.
Derek,

Onestamente, ogni volta che leggo delle "Best Practices" le prendo con un granello di sale!
AraK,

@Derek Ho trovato il documento cercando su Google "esempio di schemi di presa"; Non so chi l'abbia scritto, ma dal momento che era di un'università credo che sia affidabile. Sto cercando un esempio basato sulle informazioni fornite e ignorando la fonte.
m3th0dman,

4
@ m3th0dman "ma dal momento che proveniva da un'università credo che sia affidabile." Per me, perché proviene da un'università, lo considero inaffidabile. Non mi fido di qualcuno che non ha lavorato a progetti pluriennali parlando delle migliori pratiche nello sviluppo del software.
AraK,

1
@AraK Affidabilità non significa insindacabile; ed è quello che sto facendo qui, interrogandolo.
m3th0dman,

Risposte:


15

Bene, i tuoi primi tre punti riguardano in realtà altri principi oltre all'accoppiamento. Devi sempre trovare un equilibrio tra principi di progettazione spesso contrastanti.

Il tuo quarto punto è circa l'accoppiamento, e sono molto d'accordo con te. L'accoppiamento riguarda il flusso di dati tra i moduli. Il tipo di contenitore in cui scorrono i dati è in gran parte irrilevante. Passare una durata come doppio anziché come campo di a Tracknon elimina la necessità di passarlo. I moduli devono ancora condividere la stessa quantità di dati e avere ancora la stessa quantità di accoppiamento.

Inoltre, non riesce a considerare tutti gli accoppiamenti nel sistema come un aggregato. L'introduzione di una Trackclasse aggiunge certamente un'altra dipendenza tra due singoli moduli, ma può ridurre significativamente l'accoppiamento del sistema , che è la misura importante qui.

Ad esempio, considera un pulsante "Aggiungi a playlist" e un Playlistoggetto. L'introduzione di un Trackoggetto potrebbe essere considerata per aumentare l'accoppiamento se si considerano solo questi due oggetti. Ora hai tre classi interdipendenti anziché due. Tuttavia, questo non è l'intero sistema. È inoltre necessario importare la traccia, riprodurre la traccia, visualizzare la traccia, ecc. L'aggiunta di un'altra classe a quel mix è trascurabile.

Ora considera la necessità di aggiungere il supporto per la riproduzione di brani sulla rete anziché solo localmente. Hai solo bisogno di creare un NetworkTrackoggetto conforme alla stessa interfaccia. Senza l' Trackoggetto, dovresti creare funzioni ovunque come:

addNetworkTrack(int no, string title, double duration, URL location)

Ciò raddoppia in modo efficace il tuo accoppiamento, richiedendo anche moduli che non si preoccupano delle cose specifiche della rete per tenerne traccia, al fine di poterlo trasmettere.

Il test dell'effetto a catena è buono per determinare la vera quantità di accoppiamento. Ciò di cui ci occupiamo è limitare i luoghi in cui un cambiamento influisce.


1
+ L'accoppiamento ai primitivi continua ad accoppiarsi, indipendentemente da come viene tagliato.
Giustino

+1 per menzionare l'opzione Aggiungi un URL / effetto a catena.
user949300,

4
+1 Una lettura interessante su questo sarebbe anche la discussione del principio di inversione di dipendenza in DIP in natura dove l'uso di tipi primitivi è effettivamente visto come un "odore" di ossessione primitiva con Value Object come soluzione. A me sembra che sarebbe meglio passare un oggetto Track che un intero branco di tipi primitivi ... E se vuoi evitare la dipendenza / accoppiamento con classi specifiche, usa le interfacce.
Marjan Venema,

Risposta accettata per la piacevole spiegazione della differenza tra accoppiamento totale del sistema e accoppiamento dei moduli.
m3th0dman,

10

La mia raccomandazione è:

Uso

addTrack( ITrack t )

ma assicurati che ITracksia un'interfaccia e non una classe concreta.

L'album non conosce gli interni degli ITrackimplementatori. È associato solo al contratto definito da ITrack.

Penso che questa sia la soluzione che genera la minima quantità di accoppiamento.


1
Credo che Track sia solo un semplice oggetto bean / trasferimento dati, dove ha solo campi e getter / setter su di essi; è necessaria un'interfaccia in questo caso?
m3th0dman,

6
Necessario? Probabilmente no. Suggestivo, si. Il significato concreto di una traccia può e si evolverà, ma probabilmente ciò che la classe consumatrice richiede da essa non lo farà.
Giustino

2
@ m3th0dman Dipende sempre dalle astrazioni, non dalle concrezioni. Ciò vale indipendentemente Trackdall'essere stupido o intelligente. Trackè una concrezione. ITrackl'interfaccia è un'astrazione. In questo modo sarai in grado di avere diversi tipi di tracce in futuro, a condizione che siano conformi ITrack.
Tulains Córdova,

4
Sono d'accordo con l'idea, ma perdo il prefisso "io". Da Clean Code, di Robert Martin, pagina 24: "L'I precedente, così comune nelle mazzette legacy di oggi, è nel migliore dei casi una distrazione e troppe informazioni nel peggiore dei casi. Non voglio che i miei utenti sappiano che sto consegnando loro un interfaccia."
Benjamin Brumfield,

1
@BenjaminBrumfield Hai ragione. Neanche a me piace il prefisso, anche se lascerò la risposta per chiarezza.
Tulains Córdova,

4

Direi che il secondo metodo di esempio probabilmente aumenta l' accoppiamento, dal momento che molto probabilmente è un'istanza di un oggetto Track e la sua memorizzazione nell'oggetto Album corrente. (Come suggerito nel mio commento sopra, suppongo che sia inerente al fatto che una classe Album avrebbe il concetto di una classe Track da qualche parte al suo interno.)

Il primo metodo di esempio presuppone che un'istanza di una traccia venga istanziata al di fuori della classe Album, quindi possiamo supporre che l' istanza della classe Track non sia accoppiata alla classe Album.

Se le migliori pratiche suggerissero che non abbiamo mai un riferimento di classe a una seconda classe, l'intera programmazione orientata agli oggetti verrebbe eliminata dalla finestra.


Non vedo come avere un riferimento implicito ad un'altra classe lo renda più accoppiato che avere un riferimento esplicito. Ad ogni modo, le due classi sono accoppiate. Penso che sia meglio avere l'accoppiamento esplicito, ma non credo che sia "più" accoppiato in entrambi i modi.
TMN,

1
@TMN, l'accoppiamento extra sta nel modo in cui intendo che il secondo esempio finirà probabilmente per creare internamente un nuovo oggetto Track. L'istanza dell'oggetto è accoppiata a un metodo che altrimenti dovrebbe semplicemente aggiungere un oggetto Traccia a una sorta di elenco nell'oggetto Album (infrangendo il principio di responsabilità singola). Se il modo in cui la traccia viene creata dovesse essere modificato, anche il metodo addTrack () dovrebbe essere modificato. Questo non è così nel caso del primo esempio.
Derek,

3

L'accoppiamento è solo uno dei molti aspetti da provare nel codice. Riducendo l'accoppiamento, non stai necessariamente migliorando il tuo programma. In generale, questa è una buona pratica, ma in questo caso particolare, perché non dovrebbe Trackessere conosciuto?

Usando una Trackclasse a cui passare Album, stai rendendo il tuo codice più facile da leggere, ma soprattutto, come hai detto, stai trasformando un elenco statico di parametri in un oggetto dinamico. Questo alla fine rende la tua interfaccia molto più dinamica.

Lei dice che l'incapsulamento è rotto, ma non lo è. Albumdeve conoscere gli interni di Track, e se non si utilizzava un oggetto, Albumsarebbe necessario conoscere ogni singola informazione passata ad esso prima di poterne utilizzare lo stesso. Anche il chiamante deve conoscere gli interni Track, poiché deve costruire un Trackoggetto, ma deve comunque conoscere queste informazioni se sono state passate direttamente al metodo. In altre parole, se il vantaggio dell'incapsulamento non è la conoscenza del contenuto di un oggetto, in questo caso non potrebbe essere utilizzato poiché Albumdeve fare uso delle Trackstesse informazioni.

Il punto in cui non si desidera utilizzare Trackè se Trackcontiene una logica interna a cui non si desidera che il chiamante abbia accesso. In altre parole, se Albumfosse una classe che un programmatore che utilizzava la tua libreria avrebbe usato, non vorrai che lo usasse Trackse lo usi per dire, chiama un metodo per mantenerlo nel database. Il vero problema con questo sta nel fatto che l'interfaccia è intrecciata con il modello.

Per risolvere il problema, è necessario separare Tracki componenti dell'interfaccia e quelli logici, creando due classi separate. Per il chiamante, Trackdiventa una classe leggera che ha lo scopo di contenere informazioni e offrire piccole ottimizzazioni (dati calcolati e / o valori predefiniti). All'interno Album, useresti una classe chiamata TrackDAOper eseguire il sollevamento pesante associato al salvataggio delle informazioni dal Trackdatabase.

Certo, questo è solo un esempio. Sono sicuro che questo non è affatto il tuo caso, quindi sentiti libero di usare senza Tracksensi di colpa. Ricorda solo di tenere a mente il tuo chiamante quando stai costruendo classi e di creare interfacce quando richiesto.


3

Sono corretti entrambi

addTrack( Track t ) 

è meglio (come hai già sostenuto) mentre

addTrack( int no, String title, double duration ) 

è meno accoppiato perché il codice che utilizza addTracknon ha bisogno di sapere che esiste una Trackclasse. La traccia può essere rinominata ad esempio senza la necessità di aggiornare il codice chiamante.

Mentre stai parlando di codice più leggibile / gestibile l'articolo parla di accoppiamento . Il codice meno abbinato non è necessariamente più facile da implementare e da capire.


Vedi argomento 4; Non vedo come il secondo sia meno accoppiato.
m3th0dman,

3

Accoppiamento basso non significa nessun accoppiamento. Qualcosa, da qualche parte, deve sapere di oggetti altrove nella base di codice, e più riduci la dipendenza da oggetti "personalizzati", più motivi fornisci perché il codice cambi. Ciò che l'autore citato sta promuovendo con la seconda funzione è meno accoppiato, ma anche meno orientato agli oggetti, il che è contrario all'intera idea di GRASP come metodologia di progettazione orientata agli oggetti. Il punto è come progettare il sistema come una raccolta di oggetti e le loro interazioni; evitarli è come insegnarti come guidare un'auto dicendo che invece dovresti andare in bicicletta.

Invece, la strada giusta è ridurre la dipendenza da oggetti concreti , che è la teoria dell '"accoppiamento libero". Meno tipi concreti definiti un metodo deve conoscere, meglio è. Proprio da questa affermazione, la prima opzione è in realtà meno accoppiata, perché il secondo metodo che prende i tipi più semplici deve conoscere tutti quei tipi più semplici. Sicuramente sono integrati, e il codice all'interno del metodo potrebbe aver cura, ma la firma del metodo e i chiamanti del metodo sicuramente no . La modifica di uno di questi parametri relativi a una traccia audio concettuale richiederà ulteriori modifiche quando sono separate rispetto a quando sono contenute in un oggetto Traccia (che è il punto degli oggetti; incapsulamento).

Andando oltre, se ci si aspettasse che Track fosse sostituito con qualcosa che avesse fatto meglio lo stesso lavoro, forse un'interfaccia che definiva la funzionalità richiesta sarebbe stata in ordine, un ITrack. Ciò potrebbe consentire implementazioni diverse come "AnalogTrack", "CdTrack" e "Mp3Track" che hanno fornito informazioni aggiuntive più specifiche per quei formati, fornendo comunque l'esposizione di base dei dati di ITrack che rappresenta concettualmente una "traccia"; un sotto-pezzo audio finito. Analogamente, Track potrebbe essere una classe base astratta, ma ciò richiede di voler sempre utilizzare l'implementazione inerente a Track; reimplementarlo come BetterTrack e ora è necessario modificare i parametri previsti.

Quindi la regola d'oro; i programmi e i loro componenti di codice avranno sempre motivi per cambiare. Non puoi scrivere un programma che non richiederà mai la modifica del codice che hai già scritto per aggiungere qualcosa di nuovo o modificarne il comportamento. Il tuo obiettivo, in qualsiasi metodologia (GRASP, SOLID, qualsiasi altro acronimo o parola d'ordine che ti viene in mente) è semplicemente quello di identificare le cose che dovranno cambiare nel tempo e progettare il sistema in modo che tali modifiche siano il più facili da apportare possibile (tradotto; toccando il minor numero di righe di codice e interessando il minor numero di altre aree del sistema al di fuori dell'ambito della modifica prevista). Caso in questione, ciò che è più probabile che cambi è che una Traccia guadagnerà più membri di dati di cui o potrebbe interessare addTrack (), no tale traccia verrà sostituita con BetterTrack.

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.