Written by 20:38 API Design, DEVeloperS

Tell don’t Ask

Tell-Don’t-Ask è un principio che ci ricorda che dovremmo direttamente dire ad un oggetto cosa vogliamo che faccia piuttosto che chiedere ripetute informazioni di stato ed agire su di esse.
Questo, in altri termini, si traduce in un incoraggiamento a progettare una classe tenendo legati i dati ed i comportamenti (behavior) che operano su di essi.

Move decisions and behavior into the objects with the state.

Introduzione

Per spiegare meglio il tutto, ricordiamo che nell’object-oriented gli oggetti dialogano attraverso uno scambio di messaggi che si manifesta tramite le invocazione di metodi. Aderire correttamente al principio in esame risulta più semplice se raggruppiamo metodi e funzioni in due categorie:

  • query: chiedono e restituiscono informazioni sullo stato di un oggetto (i metodi getter ne sono un chiaro esempio).
  • command: dicono cosa fare ed innescano un cambio di stato nell’oggetto, oltre a poter restituire dei valori di comodo.

Tell-Don’t-Ask ci invoglia a scrivere un codice “timido” ed assertivo, fatto di comandi che dicano direttamente cosa vogliamo che faccia un oggetto per noi. Evitiamo di soffermarci in lunghi dibattiti fatti di richieste di stato dove al termine prendiamo una decisione che trasmettiamo all’oggetto. Questo è una dinamica che avviene nei linguaggi procedurali e non in un linguaggio orientato agli oggetti.

Procedural code gets information then makes decisions. Object-oriented code tells objects to do things.

Alec Sharp

Infatti, come suo client, non dovremmo esternalizzare delle decisioni che si basano (ed influenzano) lo stato interno dell’oggetto. La logica che implementiamo sulla base delle informazioni ottenute via query method è molto probabilmente una responsabilità dell’oggetto chiamato e non del chiamante. Decentrare questa responsabilità violerebbe un concetto cardine dell’object oriented: l’incapsulazione.
Uno dei modi migliori di tenere alla larga i bug dal nostro codice è mantenere una chiara “separation of concerns“, ovvero progettare classi e moduli in modo che abbiamo responsabilità chiare, ben definite ed isolate dal contesto esterno. Questo si realizza scrivendo codice che non riveli troppo di se e non scambi messaggi più del necessario con l’esterno.

Codice Ask-style e Tell-style

Ma facciamo parlare il codice…
Supponiamo di voler realizzare una classe CollapsiblePanelGroup per gestire un gruppo di pannelli richiudibili (CollapsiblePanel). Inoltre, vogliamo che solo un pannello per volta possa rimanere esploso (aperto).
Senza addentrarci troppo in un esempio che rischia di diventare troppo cervellotico, supponiamo di voler implementare il codice del metodo che andrà a gestire l’handler che comanda l’apertura di uno dei pannelli.

Questo è quello che si avrebbe nel caso di codice in ask-style

List<CollapsiblePanel> panels = panelGroup.getPanels();
if(panels != null && panels.size() > 0 ) {
	for(int i=0; i<panelGroup.getPanels().size(); i++) {
		if(panels.get(i).isOpened() && i != indexToOpen) {
			panels.get(i).setOpened(false);
			panels.get(i).hideContent();
		} else if(i == indexToOpen && !panels.get(i).isOpened()) {
			panels.get(i).setOpened(true);
			panels.get(i).showContent();
		}
	}
}

Viceversa, il principio Tell-Don’t-Ask verte verso una soluzione differente, diretta e dichiarativa, che esponga meno dello stato interno dell’oggetto. In quest’ottica, il codice tell-style potrebbe essere questo…

panelGroup.open(indexToOpen);

Dov’è finito tutto il resto? Direttamente trasposto nel metodo panelGroup.open(...) della classe CollapsiblePanelGroup, che accetta come parametro l’indice del pannello da aprire.
Come possiamo apprezzare, la nuova soluzione è più fluida e leggibile e non espone nulla della struttura interna dell’oggetto. Quest’ultima punto è molto importante perché quanto più esponiamo fuori, tanto più aumentiamo l’accoppiamento nel codice con altri oggetti di dominio. Giusto per intenderci, esponendo e modificando la lista di CollapsiblePanel nel codice ask-style, stiamo creando un legame tra il nostro codice e questa classe. Se non ci rendiamo conto del come, facciamo due semplici esempi di accomppiamento.

  • Supponiamo di aver inavvertitamente modificato lo stato di un pannello impostando a true l’attributo opened al di fuori del ciclo for, ma senza aver richiamato il metodo showContent( ). Per come è stata impostata la logica nel ramo if-else, avremmo uno stato inconsistente (riflettiamoci un po’ su).
  • Supponiamo che cambi la firma del metodo showContent( ) di CollapsiblePanel. I cambi che saranno necessari nel codice si estenderanno non solo alla classe CollapsiblePanelGroup ma anche al codice che la utilizza.

In aggiunta a tutto quanto già detto, lo stato di un oggetto non solo appartiene ad esso ma potrebbe anche perdere o assumere un senso errato al di fuori di esso (la stringa “rosso” potrebbe appartenere alla classe Semaforo e rappresentare uno stato ma anche alla classe Bandiera ed essere un colore).

La legge di Demetra

Come detto, con più oggetti dialoghiamo tanto più aumentiamo il rischio di problemi laddove avvengano dei cambi. Per questo non solo dovremmo esporre il meno possibile il nostro stato, ma anche ridurre al minimo il numero di oggetti con cui scambiamo messaggi.
Ad aiutarci in questo è anche la legge di Demetra.
In via del tutto formale, questa legge dice che ogni metodo di un oggetto può invocare solamente i metodi dei seguenti oggetti:

  • i propri;
  • dei suoi parametri;
  • di ogni oggetto che crea;
  • dei propri componenti diretti.

La legge lascia fuori dalla lista i metodi degli oggetti ritornati da un altro oggetto (che è quanto facciamo nell’esempio quando scriviamo ad esempio panel.get(i).isOpened()).
Avere un unico, autoritativo metodo che fa esattamente quello che vogliamo ci evita di accedere a componenti interne e di introdurre nel nostro codice delle dipendenze con la struttura interna della classe che utilizziamo.

(Visited 102 times, 1 visits today)

Close