ISP - Interface Segregation Principle

La I di S.O.L.I.D. sta per Interface Segregation Principle, e si occupa di ottimizzare il lavoro delle interface in modo intelligente.
francesco
Francesco Malatesta
06/09/2015 in

Hai mai sentito parlare dei Principi S.O.L.I.D. nella programmazione PHP?

Scopriamoli insieme, in questa mini-serie dedicata all'argomento!

Il Principio

La "I" di S.O.L.I.D. sta per Interface Segregation Principle (da ora ISP), o Principio di Segregazione delle Interfacce. Abbastanza corto, presenta il seguente enunciato:

"No client should be forced to depend on methods it does not use."

Possiamo tradurlo così:

"Nessun client dovrebbe essere forzato a dipendere da metodi che non usa."

Il principio è stato proposto da "Zio Bob" Robert C. Martin, durante una sua consulenza alla Xerox. L'azienda aveva sviluppato un sistema per la gestione di stampe e fax, tuttavia troppo lento per gli standard richiesti.

Zio Bob ha risolto brillantemente la situazione, appunto, formulando (ed applicando) quello che poi è diventato l'ISP.

La Spiegazione

Se hai letto le sezioni dedicate agli altri principi S.O.L.I.D. avrai sicuramente notato che, ogni tanto, ho suggerito di applicare una certa soluzione tramite le interfacce. Le necessità di astrazione sono pane quotidiano ed è vitale scrivere un software che sappia astrarre al punto giusto i suoi componenti per poter essere scalabile quanto serve e quando serve.

Come ben sai, un'interfaccia è una sorta di "contratto" che definisce alcune funzionalità tramite dichiarazioni di metodi. Nel rispettare questo contratto, le classi devono necessariamente implementare tali metodi.

Nella prima "lezione" hai scoperto il Single Responsibility Principle, che "esorta" una classe a svolgere una ed una sola operazione. In modo simile, applicare l'ISP significa dividere un'interfaccia "grande" in tante piccole interfacce, in modo tale da non doversi portare dietro tanti metodi quando non servono.

Nel gergo tecnico, queste piccole interfacce ottenute vengono chiamate "Role Interface", proprio perchè definiscono dei "ruoli" da assegnare alle classi.

Vediamo un tipico esempio di ISP violato e come porvi rimedio.

L'Esempio

Supponiamo di avere a che fare con un sistema che gestisce dei mezzi di trasporto. In una prima iterazione ci occuperemo esclusivamente di automobili. Avremo svariate classi per ogni tipologia di auto, ma di partenza implementeremo innanzitutto un'interfaccia.

<?php

	interface VeichleInterface {

		public function turnOn();
		public function turnOff();

		public function accelerate();
		public function brake();

		public function lightsOn();
		public function lightsOff();

		public function radioOn();
		public function radioOff();
		public function switchToUsbMusic();
		public function switchToRadioMusic();

	}

Come vedi, questa interfaccia offre svariati metodi. I nomi sono abbastanza descrittivi: non credo ci sia bisogno di spiegare a cosa servono.

Una volta definita l'interfaccia potremo usarla senza problemi nelle classi:

<?php

	class TeslaVeichle implements VeichleInterface {

		public function turnOn() 
		{
			// implemento il metodo...
		}

		public function turnOff() 
		{
			// implemento il metodo...
		}

		public function accelerate() 
		{
			// implemento il metodo...
		}

		// implemento tutti i metodi richiesti dall'interfaccia...

		public function switchToRadioMusic() 
		{
			// implemento il metodo...
		}

	}

Insomma: pare funzionare tutto. Il capo si complimenta con noi: abbiamo fatto un ottimo lavoro, in effetti.

Dopo qualche giorno, tuttavia, tornano i nuvoloni. Il capo entra in ufficio, è lunedì mattina e sei ancora in modalità zombie. La voce squillante del boss annuncia:

Ottimo, abbiamo la partnership! Dobbiamo estendere il supporto della nostra applicazione alle macchine d'epoca e alle astronavi!

Non ci stai credendo: astronavi? Beh, nella programmazione a volte succede anche questo.

La prima cosa che fai è quindi andare a modificare l'interfaccia.

interface VeichleInterface {

		public function turnOn();
		public function turnOff();

		public function accelerate();
		public function brake();

		public function lightsOn();
		public function lightsOff();

		public function radioOn();
		public function radioOff();
		public function switchToUsbMusic();
		public function switchToRadioMusic();

		// metodi aggiunti per il volo...
		public function takeOff();
		public function land();
		public function sensorsCheck();

	}

Abbiamo modificato l'interfaccia per aggiungere le funzionalità del volo. A questo punto, dove necessario, aggiungeremo queste nuove funzionalità...

In caso contrario? Le Tesla sono stupende, ma non volano ancora! Figuriamoci le macchine d'epoca...

Beh, potresti sempre scrivere dei metodi che ritornano "null" o qualcosa del genere...

BLEAH. Fermo! Scherzo ovviamente, non fare mai una cosa del genere.

La verità è che dovremmo ragionare in modo totalmente diverso. L'ISP viene in nostro soccorso e ci dice chiaramente quello che dobbiamo fare: dividere questa mega interfaccia in tante più piccole. La divisione dovrebbe avvenire per funzionalità (ruoli, ricordi?), permettendo alle classi interessate di dover implementare solo quelle desiderate.

Dall'interfaccia vista prima, quindi, arriveremmo a...

<?php

interface EngineVeichleInterface {

	public function turnOn();
	public function turnOff();

}

interface BasicVeichleInterface {

	public function accelerate();
	public function brake();

}

interface LightsVeichleInterface {

	public function lightsOn();
	public function lightsOff();

}

interface RadioVeichleInterface {

	public function radioOn();
	public function radioOff();
	public function switchToUsbMusic();
	public function switchToRadioMusic();

}

interface FlyingVeichleInterface {

	public function takeOff();
	public function land();
	public function flySensorsCheck();

}

Adesso si che va meglio! Abbiamo un sacco di interfacce, da implementare dove necessario! Guarda tu stesso questi esempi.

<?php

	class OldCar implements 
		EngineVeichleInterface, 
		BasicVeichleInterface, 
		LightsVeichleInterface {

			public function turnOn()
			{
				// implemento il metodo...
			}

			public function turnOff()
			{
				// implemento il metodo...
			}

			public function accelerate()
			{
				// implemento il metodo...
			}

			public function brake()
			{
				// implemento il metodo...
			}

			public function lightsOn()
			{
				// implemento il metodo...
			}

			public function lightsOff()
			{
				// implemento il metodo...
			}

		}

Come vedi, in questa classe OldCar che rappresenta una macchina d'epoca ho usato l'ISP per implementare solo i metodi strettamente necessari. Tali macchine hanno un motore e delle luci: nessuna traccia però del sistema di volo!

Se ci fai caso, come già detto prima, nell'applicare l'ISP stiamo applicando, anche ed indirettamente, il SRP!

Sono sicuro tu abbia compreso bene il principio: generalmente è uno dei più semplici vista la semplicità dell'enunciato e degli esempi a corredo. Come sempre, se hai dubbi non esitare e lascia un commento. Siamo qui per aiutarci, d'altronde!

Altrimenti, vai verso l'ultima parte, dedicata alla Dependency Inversion.