Home>>Il principio di inversione della dipendenza (DIP)

La realizzazione di un'applicazione software secondo la metodologia OOP consiste di un insieme di oggetti che comunicano tra loro ed il più delle volte l'analisi dell'architettura rivela una fitta maglia di interdipendenze tra le classi anche tra quelle poste a livelli di astrazione differente. In realta questa situazione è l'effetto di una cattiva progettazione e comporta una serie di vincoli e di svantaggi di difficile soluzione.

Le numerose dipendenze impediscono il riuso di parti del codice in altri contesti, poiché diventa quasi impossibile isolare le classi di interesse dal resto dell'applicazione; anche le modifiche risultano complesse poiché i cambiamenti ad una classe possono comportare ulteriori cambiamenti ad un numero imprecisato di altre classi o generare malfunzionamenti su altre parti del codice.

Il principio di inversione della dipendenza è una regola che aiuta a ridurre le dipendenze tra le classi.

La stratificazione del software

È abitudine comune strutturare l'architettura di un software orientato ad oggetti su più livelli: ciascuno strato definisce ed esporta un insieme coerente di servizi tramite interfacce ben definite.

Un'interpretazione ingenua di tale principio potrebbe spingere un progettista a strutturare il proprio codice come rappresentato in figura 1. In questo diagramma le classi del livello policy utilizzano le classi dello strato sottostante (Mechanism layer), che a loro volta sfruttano i servizi del livello più basso.

Figura 1: Struttura su più livelli

Figura 1: Struttura su più livelli

Sebbene questa scelta architetturale possa sembrare appropriata, in realtà nasconde un problema insidioso: il Policy layer è sensibile alle modifiche dei livello Mechanism, che a sua volta dipende dalle modifiche del livello Utility.

Un modello più appropriato è rappresentato in figura 2: rispetto all'architettura precedente sono state aggiunte due package di classi astratte che rappresentano le interfacce verso i livelli inferiori.

Figura 2: Struttura su più livelli con classi astratte

Figura 2: Struttura su più livelli con classi astratte

Ciasuna classe concreta del modello precedente estende la relativa classe astratta, mentre le classi del livello più alto utilizzano le classi dello strato più basso attraverso la classe astratta; in questo modo nessun livello dipende dalle classi del livello adiacente, ma solo dalle classi astratte. Questa modifica all'archiettura spezza le dipendenze tra i livelli che diventano insensibili ad eventuali cambiamenti che potrebbero essere introdotti in quelli sottostanti.

Fondamentalmente è stato invertito il senso della dipendenza, non sono più le classi delgli strati superiori a dipendere da quelle degli strati inferiori, ma viceversa: le classi dei livelli più bassi dipendono dall'interfaccia definita nelle classi astratte.

Definizione del principio di inversione della dipendenza

Il principio di inversione della dipendenza afferma che:

I moduli del livello superiore non dovrbbero dipendere dai moduli dello strato inferiore
Le astrazioni non dovrebbero dipendere dai dettagli. I dettagli dovrebbero dipendere dalle astrazioni

L'applicazione di questo principio contribuisce alla creazione di architetture in cui gli strati più alti siano più facilmente riutilizzabili in altri contesti ed insensibili alle modifiche dei livelli inferiori dell'architettura.

Esempio

Il principio di inversione della dipendenza può essere applicato ogni volta che una classe invia un messaggio ad un'altra. Un sempice esempio può essere di aiuto per chiarire i concetti esposti nei paragrafi precedenti.

Si supponga di voler realizzare un sistema per controllare l'accensione e lo spegnimento di alcuni dispositivi domestici (lampadine, ventilatori, TV, ecc.); la figura 3 rappresenta l'archiettura delle classi per il controllo di una lampadina.

Figura 3: Sistema per il controllo di una lampadina

Figura 3: Sistema per il controllo di una lampadina

Come si nota dalla figura 3, la classe Button contiene un puntatore alla classe Lamp, che controlla tramite i messaggi TurnOn() e TurnOff().

001 //-------------lamp.h----------------
002class Lamp
003{
004public:
005void TurnOn();
006void TurnOff();
007};
008
009//------------button.h---------------
010class Lamp;
011class Button
012{
013public:
014  Button(Lamp& l)
015  : _pLamp(&l)
016  {}
017  
018  bool IsOn();
019private:
020  Lamp* _pLamp;
021};
022
023//------------button.cpp-------------
024#include “button.h”
025#include “lamp.h”
026
027void Button::OnClick()
028{
029if (IsOn())
030  _pLamp->TurnOff();
031else
032  _pLamp->TurnOn();
033}

Dal listato 1 si evince che Button è la classe che contiene la logica di controllo e sarebbe auspicabile poterla riutilizzare per gestire anche altri dispositivi. Come si nota dalla figura la classe Button dipende direttamente dalla classe Lamp, a livello di codice C++ tale dipendenza si traduce nella necessità di includere il file lamp.h nel file Button.cpp (vedi listato 1); tale dipendenza comporta che la classe Button debba essere modificata o almeno ricompilata ogni volta che si modifica la classe Lamp. Inoltre, la dipendenza dalla classe Lamp, rende impossibile utilizzare la classe Button per controllare altri tipi di dispositivi, senza introdurre pesanti modifiche.

Figura 4: Architettura conforme al principio di inversione della dipendenza

Figura 4: Architettura conforme al principio di inversione della dipendenza

La figura 4 mostra un'architettura migliore, conforme al principio di inversione della dipendenza, la catena delle dipenze è stata invertita introducendo due classi astratte GenericButton ed GenericDevice, che rappresentano rispettivamente l'interfaccia del pulsante e della lampadina.

Come si può notare dal diagramma tutta la logica di alto livello è confinata nella classe GenericButton che rimane indipendente dai dettagli dell'implementazione ed anche dal dispositivo che si vuole controllare. In questo modo è possibile riutilizzare la classe Button per controllare anche altri dispositivi, purché implementino l'interfaccia richiesta (GenericDevice).

Segue il listato C++ che implementa l'architettura di Figura 4.

001//---------genericbutton.h-----------
002class GenericDevice;
003class GenericButton
004{
005public:
006  GenericButton(GenericDevice& d);
007  virtual bool IsOn() = 0;
008  void OnClick();
009private:
010  GenericDevice* _pDev;
011};
012
013//---------genericdevice.h-----------
014class GenericDevice
015{
016public:
017  virtual void TurnOn() = 0;
018  virtual void TurnOff() = 0;
019};
020
021//------------button.h---------------
022#include “GenericButton.h”
023class Button : public GenericButton
024{
025public:
026  Button(GenericDevice& dev);
027  virtual bool IsOn();
028};
029
030//---------genericbutton.cpp---------
031#include “GenericButton.h”
032#include “GenericDevice.h”
033GenericButton::GenericButton(GenericDevice& d)
034: _pDev(&d)
035{}
036
037void GenericButton::OnClick()
038{
039  if (IsOn())
040    _pDev->TurnOn();
041  else
042    _pDev->TurnOff();
043}

Conclusioni

Quando si applica questo principio, le classi di livello più alto non lavorano direttamente con quelle dei livelli inferiori, ma utilizzano delle classi astratte come interfacce. In questo caso, se dovesse essere necessario instanziare nel livello superiore delle classi dei livelli sottostanti, per mantenere l'indipendenza, non è possibile utilizzare direttamente l'operatore new, ma sarà necessario affidarsi a design pattern quali: Factory Method, Abstract Factory, Prototype. E' evidente che l'impiego di questo principio implica uno sforzo più grande da parte del progettista ed una maggiore complessità de codice, ma a fronte di una maggiore flessibilità.

Vota questo articolo per primo

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Commenti

Aggiungi commento




  Country flag

biuquote
  • Commento
  • Anteprima
Loading



Calendario

<<  settembre 2010  >>
lumamegivesado
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910

View posts in large calendar
Licenza d'uso
Eccetto dove diversamente specificato, i contenuti di questo sito sono rilasciati mediante:

Licenza Creative Common