Popolarizzata dall’ottimo libro “The Pragmatic Programmer”, la legge di Demetra è anche conosciuta come Principle of Least Knowledge (“principio della conoscenza minima”) e suggerisce che una classe non dovrebbe conoscere i dettagli implementativi di un’altra classe. O alternativamente, una classe dovrebbe conoscere solamente i dettagli necessari a svolgere le proprie funzioni.
Un classico esempio di violazione di questo principio è il seguente:
public Shop{
List<Item> items;
Shop(Context context){
this.items = context.items;
}
}
Per creare un oggetto di tipo Shop dovremo fornire un’istanza di Context. Lo shop però, non fa altro che accedere ad un campo di Context per estrarre ciò di cui ha realmente bisogno, ovvero una lista di items.
Un altro caso piuttosto comune è il passaggio di un oggetto Config
in input alle varie classi che poi estraggono i valori di cui hanno bisogno.
Che problema c’è a passare un Context nel costruttore?
Riutilizzare lo stesso codice in un nuovo progetto è appena diventato molto più difficile. Questo perchè adesso avremo bisogno della libreria dove è stata definita questa classe.
Potrebbe non sembrare molto oneroso come import, ma la situazione tende a degenerare velocemente in quanto spesso nel Context vengono usate classi di altre possibili librerie che adesso dovremo importare ma di cui non avremo bisogno.
Ovviamente, rompendo la legge di Demetra, il nostro codice è diventato in generale difficile da riutilizzare in quanto altamente accoppiato (high coupling) col resto del sistema. Essendo il coupling una proprietà transitiva, la nostra classe ha appena ereditato tutti i difetti del resto del nostro sistema.
Infine, bisognerà fornire dettagli nel JavaDoc per spiegare quali sono gli effettivi input di questa classe. Dal costruttore infatti, si direbbe che c’è bisogno di fornire un oggetto di tipo Context. Conoscendo il costruttore, sappiamo che la classe Shop ha bisogno di accedere al campo items del contesto che quindi dovrà essere definito (non nullo).
Il testing diventa complicato
Come suggerisce l’ultimo punto, il testing diventa molto più complicato. Per creare una istanza di Shop, dovremo creare un Context e capire come inizializzarlo. A volte infatti, questi Context hanno costruttori molto grandi.
Per inizializzare questa classe, saremo costretti a sporcare i test inserendo codice di inizializzazione non utili ai fini del testing. Oltretutto, rendono il codice più difficile da manutenere.
Conclusione
La legge di Demetra è sicuramente molto importante per la corretta progettazione di una classe nel campo della OOP. Rispettare questa regola assicura al nostro codice proprietà importanti come l’alta coesione e il basso accoppiamento.
Per concludere, immagina scrivere un test per questa classe:
class Monitor {
SparkPlug sparkPlug;
Monitor(Context context) {
this.sparkPlug = context.
getCar().getEngine().
getPiston().getSparkPlug();
}
}
Referenze
- https://web.archive.org/web/20200219224531/http://misko.hevery.com/2008/07/18/breaking-the-law-of-demeter-is-like-looking-for-a-needle-in-the-haystack/
- https://en.wikipedia.org/wiki/Law_of_Demeter