risorse | black box e dependency injection

Black Box e Dependency Injection

L'astrazione è uno strumento fondamentale per gestire la progettazione di sistemi complessi: nascondendo i dettagli, si presuppone di poter dominare la complessità.

           ???
            v
   +-----------------+
   |                 |
   |                 |
   | implementazione |
   |                 |
   |                 |
   +-----------------+

   per interagire con un sistema complesso,
   è necessario prima di tutto comprenderlo

Un'astrazione nasconde i dettagli implementativi con un'interfaccia che definisce la modalità di attivazione delle funzionalità, a prescindere dall'implementazione.

   +-----------------+
   |   interfaccia   |
   +-----------------+
           v
   +-----------------+
   |                 |
   |                 |
   | implementazione |
   |                 |
   |                 |
   +-----------------+

   per interagire con un'astrazione,
   è sufficiente comprenderne l'interfaccia

A volte tuttavia non si può prescindere da tutti i dettagli implementativi. A fronte di differenti opzioni implementative, quale dovrebbe scegliere un buon progettista? In linea di principio, quella in grado di soddisfare ogni possibile utilizzatore del sistema. E se – come spesso accade – una tale implementazione non esiste? Un caso del genere si definisce mapping dilemma, e può essere risolto tramite una scelta arbitraria del progettista (si parla in questo caso di mapping decision). L'utilizzatore non ha modo di intervenire su questa scelta; nel caso non infrequente che la scelta del progettista non sia quella ottimale per l'utilizzatore, si realizza un mapping conflict.

     ^             ^
   +-^-------------^-+
   | ^ interfaccia ^ |
   +-^-------------^-+
     ^      v      ^
   +-^-------------^-+
   | ^             ^ |
   | ^             ^ |
   | implementazione |
   |                 |
   |                 |
   +-----------------+

   un'astrazione mal progettata
   costringe gli utilizzatori a comprenderne
   comunque i dettagli implementativi

All'utilizzatore restano due possibilità:

  1. reimplementare la funzionalità secondo le proprie esigenze;
  2. dedurre l'implementazione per forzare, se possibile, la libreria ad operare in modo accettabile.

La prima opzione ha come conseguenza la duplicazione di funzionalità, mentre la seconda, oltre a costituire un evidente controsenso, risulta essere poco resiliente (si pensi al caso in cui una nuova versione del componente cambia implementazione). La conclusione è quindi che l'utilizzatore deve poter intervenire sulle scelte implementative, partecipando alle decisioni. Non potendo farlo in fase di progettazione, deve poterlo fare in fase di esecuzione. Si delineano perciò due tipi di interfacce: una interfaccia base, che rappresenta il punto d'accesso alle funzionalità di libreria, ed una meta-interfaccia, utilizzabile per specificare le modalità operative con cui si desidera la libreria funzioni. Occupandosi di aspetti ortogonali (la prima del cosa, la seconda del come), le due interfacce devono essere indipendenti e separate.

   +-----------------+
   |   interfaccia   |
   |      base       |
   +-----------------+
            v
   +-----------------+
   |                 |
   |                 |     +------------------+     +-------------------+
   | implementazione |  >  | meta-interfaccia |  >  | personalizzazioni |
   |                 |     +------------------+     +-------------------+
   |                 |
   +-----------------+

Tramite l'interfaccia base l'utilizzatore attiva le funzionalità di libreria; per mezzo della meta-interfaccia, l'implementazione attiva le personalizzazioni.

La meta-interfaccia definisce un protocollo, che nel caso dell'OOP viene detto meta-object protocol.

Come vanno progettate le meta-interfacce?

Innanzitutto va tenuto conto che essendo esse stesse astrazioni, si applicano loro le stesse considerazioni oggetto di questa analisi. In secondo luogo, i mapping dilemma vengono risolti isolando le mapping decision e rendendole sostituibili: gli utilizzatori che sperimentano un mapping conflict possono realizzare una mapping decision differente e chiedere al sistema di farne uso.

I principi ispiratori alla base della buona progettazione delle meta-interfacce sono:

Nota

A proposito di astrazioni di sistemi complessi, Anders Hejlsberg (Turbo Pascal, Delphi, C#) introduce il concetto di simplessità:

When you take something incredibly complex and try to wrap it in something simpler, you often just shroud the complexity. You don't actually design a truly simple system. And in some ways you make it even more complex, because now the user has to understand what was omitted that they might sometimes need. That's simplexity.

La presenza di un'interfaccia astratta non deve quindi giustificare eventuali deficienze strutturali dell'implementazione sottostante.

Riferimenti

  1. Martelli, Alex. "Lo Zen e l'Arte della Manutenzione delle Astrazioni". PyCon Tre. <http://www.pycon.it/conference/talks/lo-zen-e-larte-della-manutenzione-delle-astrazioni>. Visitato il 20 Maggio 2011.
  2. Hejlsberg, Anders. "Delegates, Components, and Simplexity". Aritma developer. <http://www.artima.com/intv/simplexity.html>. Visitato il 5 Ottobre 2011.
  3. Kiczales, Gregor. "Why are Black Boxes so Hard to Reuse?" Palo Alto Research Center. <http://www2.parc.com/csl/groups/sda/projects/oi/towards-talk/transcript.html>. Visitato il 5 Ottobre 2011.
  4. Spolsky, Joel. "The Law of Leaky Abstractions". Joel on Software. <http://www.joelonsoftware.com/articles/LeakyAbstractions.html>. Visitato il 5 Ottobre 2011.

Pagina modificata l'8/11/2011