Accoppiamento, coesione e legge di Demeter

La legge di Demetra indica che dovresti parlare solo con oggetti che conosci direttamente. Cioè, non eseguire il metodo di concatenazione per parlare con altri oggetti. Quando lo fai, stai stabilendo collegamenti impropri con gli oggetti intermedi, accoppiando in modo inappropriato il tuo codice con un altro codice.

Questo è male.

La soluzione sarebbe per la class di cui si sa che espone essenzialmente semplici wrapper che delegano la responsabilità all’object con cui ha una relazione.

Quello è buono.

Ma sembra che la class abbia una bassa coesione . Non è più semplicemente responsabile esattamente di ciò che fa, ma ha anche i delegati che, in un certo senso, rendono il codice meno coeso duplicando parti dell’interfaccia del relativo object.

Questo è male.

Porta davvero ad abbassare la coesione? È il minore dei due mali?

È questa una di quelle aree di sviluppo grigie, in cui è ansible discutere di dove si trova la linea, o ci sono dei modi forti e principi per prendere una decisione su dove tracciare la linea e su quali criteri utilizzare per prendere questa decisione?

Grady Booch in “Analisi e progettazione orientata agli oggetti”:

“L’idea di coesione viene anche dal design strutturato: in sostanza, la coesione misura il grado di connettività tra gli elementi di un singolo modulo (e per il design orientato agli oggetti, una singola class o object). coesione, in cui astrazioni del tutto indipendenti sono gettate nella stessa class o modulo.Per esempio, si consideri una class comprendente le astrazioni di cani e veicoli spaziali, i cui comportamenti sono abbastanza estranei.La forma di coesione più desiderabile è la coesione funzionale, in cui gli elementi di una class o di un modulo lavorano tutti insieme per fornire alcuni comportamenti ben delimitati.Così, la class Dog è funzionalmente coesiva se la sua semantica abbraccia il comportamento di un cane, l’intero cane e nient’altro che il cane. ”

Sottotitolo Dog with Customer in quanto sopra e potrebbe essere un po ‘più chiaro. Quindi l’objective è in realtà solo mirare alla coesione funzionale e allontanarsi il più ansible dalla coesione casuale. A seconda delle astrazioni, questo può essere semplice o richiedere alcuni refactoring.

Nota la coesione si applica tanto a un “modulo” che a una singola class, cioè un gruppo di classi che lavorano insieme. Quindi in questo caso le classi cliente e ordine hanno ancora una coesione decente perché hanno questo forte rapporto, i clienti creano gli ordini, gli ordini appartengono ai clienti.

Martin Fowler dice che sarebbe più comodo chiamarlo “Suggerimento di Demeter” (leggi l’articolo Mock non sono stub ):

“I tester mockisti parlano più di evitare ‘treni distruttivi’ – catene di metodi di stile di getThis (). GetThat (). GetTheOther (). Evitare catene di metodi è anche noto come segue la legge di Demeter. anche il problema opposto degli oggetti degli uomini di mezzo gonfiati con i metodi di inoltro è un odore (ho sempre pensato che sarei stato più a mio agio con la Legge di Demetra se fosse stato chiamato il Suggerimento di Demetra ). ”

Questo riassume bene da dove vengo: è perfettamente accettabile e spesso necessario avere un livello di coesione più basso di quello che potrebbe richiedere la stretta osservanza della “legge”. Evitare la coesione casuale e puntare a una coesione funzionale, ma non rimanere impigliato nel tweaking dove necessario per adattarsi in modo più naturale all’astrazione del design.

Se stai violando la Legge di Demetra avendo

int price = customer.getOrder().getPrice(); 

la soluzione non è creare getOrderPrice () e trasformare il codice in

 int price = customer.getOrderPrice(); 

ma invece di notare che questo è un codice olfattivo e apportare le modifiche rilevanti che, auspicabilmente, entrambi aumentano la coesione e l’accoppiamento inferiore. Sfortunatamente non c’è un semplice refactoring qui che si applica sempre, ma probabilmente dovresti applicare tell non chiedere

Penso che potresti aver frainteso cosa significa coesione. Una class che è implementata in termini di diverse altre classi non ha necessariamente una bassa coesione, purché rappresenti un concetto chiaro e abbia uno scopo chiaro. Ad esempio, potresti avere una class Person , che viene implementata in termini di classi Date (per data di nascita), Address e Education (un elenco di scuole a cui la persona è andata). Puoi fornire involucri in Person per ottenere l’anno di nascita, l’ultima scuola a cui la persona è andata o lo stato in cui vive, per evitare di esporre il fatto che la Person è implementata in termini di quelle altre classi. Ciò ridurrebbe l’accoppiamento, ma renderebbe la Person non meno coesa.

È un’area grigia. Questi principi sono pensati per aiutarti nel tuo lavoro, se trovi che stai lavorando per loro (cioè si stanno intromettendo e / o trovi che complica il tuo codice) allora ti stai conformando troppo e hai bisogno arretrare.

Fallo funzionare per te, non lavorare per questo.

Non so se questo abbassa davvero la coesione.

Aggregazione / composizione sono tutte basate su una class che utilizza altre classi per soddisfare il contratto che espone attraverso i suoi metodi pubblici. La class non ha bisogno di duplicare l’interfaccia dei suoi oggetti correlati. In realtà nasconde qualsiasi knwowledge su queste classi aggregate dal chiamante del metodo.

Per obbedire alla legge di Demeter nel caso di più livelli di dipendenza di class, è sufficiente applicare aggregazione / composizione e buon incapsulamento ad ogni livello.

In altre parole, ogni class ha una o più dipendenze su altre classi, tuttavia queste dipendono solo sempre dalla class di riferimento e non da qualsiasi object restituito da strutture / metodi.

Nelle situazioni in cui sembra esserci un compromesso tra accoppiamento e coesione, probabilmente mi chiedo “se qualcun altro avesse già scritto questa logica, e stavo cercando un bug in esso, dove guarderei prima?”, E scrivi il codice in questo modo.