Articoli Manifesto Tools Links Canali Libri Contatti ?
Design / OOP

Principi del Design Orientato agli Oggetti

Abstract
L'attuale produzione di software professionale vede l'ausilio di molti CASE Tools che permettono di accelerare il processo di sviluppo e di richiedere competenze specialistiche o meno stringenti ai codificatori. Se è indubbia l'utilità di questi tool, è anche vero che molti problemi derivano dalla degenerazione nel loro uso e nell'ignorare, o aver dimenticato, semplici principi di design da parte dei developer.
Data di stesura: 07/01/2003
Data di pubblicazione: 09/02/2003
Ultima modifica: 04/04/2006
di Stefano Fago Discuti sul forum   Stampa

Architettura e Design.

Il significato del termine architettura software ha molte sfaccettature e può essere esplicato a differenti livelli di astrazione. Al livello più alto possono emergere i differenti pattern architetturali che delineano l'architettura stessa e la struttura generale del software. Scendendo di livello l'architettura esprime gli scopi del dato software: il "cosa" e non il "come". Scendendo ulteriormente si evidenzia la struttura dei moduli software e le loro interconnessioni. Giunti a questo punto ci si addentra sempre più verso il mondo del Design dove, oramai da un decennio, sono preponderanti i Design Pattern, in particolare quelli della Gang of Four.
Questi elementi concettuali hanno posto le basi per il riuso del design nei progetti sviluppati con linguaggi orientati agli oggetti e per la nascita di criteri di progettazione alla base di molte architetture software moderne. In realtà quanto usato dagli odierni designer ha delle basi più profonde, dei principi che pur nella loro genericità sono linee guida fondamentali per qualsiasi progettista.

Principi di Design ad Oggetti

Il più importante dei principi che esamineremo è indicato come Open Closed Principle (OCP) secondo il quale " un modulo dovrebbe essere aperto alle estensioni ma chiuso alle modifiche".
Il senso di questo principio è nel cercare di realizzare dei moduli che siano facili da estendere senza doverli modificare: possibilità di modificare quello che fa un modulo, senza doverne modificare il codice sorgente.
Nella pratica di ogni giorno seguire tale principio è possibile grazie al supporto dei linguaggi orientati agli oggetti secondo due direttive: polimorfismo dinamico e polimorfismo statico. Quest'ultimo non è presente per tutti i linguaggi in quanto relazionato alla tecnologia dei template.
Nel caso del polimorfismo dinamico ci si riferisce alla presenza di un tipo usato come elemento generico al cui contratto si rifanno tutte le possibili varianti implementative di un'astrazione.
Vediamo un piccolo esempio in Java.
Supponiamo di avere un programma di disegno bidimensionale, grazie al quale attuare il rendering di semplici figure geometriche come triangoli, cerchi e quadrati. Un primo approccio è quello di costruire classi che esprimano le differenti figure d'interesse nonché una classe Manager per l'azione di rendering. Quest'ultima entità deve sapere come disegnare i differenti elementi e quindi si può ipotizzare un metodo paint nella forma seguente:
public void paint(int type, int args[])
{
  if(type == 0) 
    drawCircle(args);
  else if (type == 1)
    drawSquare(args);
  else if( type == 2)
    drawTriangle(args);
}
È evidente che un approccio di questo tipo vìola l'OCP dato che per comprendere una nuova figura, ad esempio un'ellisse, saremmo costretti ad alterare il codice del metodo paint. Una soluzione migliore è quella di definire una classe astratta Shape, fornita del metodo paint, che esprime il concetto di figura geometrica. Le classi concrete saranno specializzazioni della classe Shape e, quindi, il Manager potrà trattare in modo uniforme triangoli, quadrati, cerchi ed altre figure geometriche inizialmente non previste. Avremo quindi il codice nella forma seguente:
abstract class Shape
{
  ...
  public abstract void paint();
  ...
} 


class Circle extends Shape
{
  public void paint()
  {
    ...
  }
}

class Square extends Shape
{
  public void paint()
  {
    ...
  }
}

class Triangle extends Shape
{
  public void paint()
  {
    ...
  }
}

class Manager
{
  ...
  public void paint(Shape s)
  {
    s.paint(); 
  }
  ...
}
L'architettura presentata permette al manager di trattare qualsiasi tipo di figura geometrica dato anche il fatto che ogni elemento conosce il modo più opportuno di attuare l'azione di rendering.

L'OCP esprime l'obiettivo di un'architettura ma il meccanismo per raggiungere tale obiettivo è espresso dal Dependency Inversion Principle (DIP) secondo il quale "è necessario dipendere dalle astrazioni e non da elementi concreti". Questo principio emerge dall'osservazione che nei sistemi sviluppati con l'approccio strutturato si creano catene di dipendenza tra i diversi moduli in un modo tale che specifiche logiche o algoritmi condizionano elementi ad un più alto livello di astrazione. Nel mondo ad oggetti le astrazioni permettono di disaccoppiare dettagli implementativi dalle logiche essenzialmente legate al dominio del problema. Un esempio di quanto esposto è riscontrabile nel codice presentato in precedenza: l'uso di una classe astratta Shape permette di non occuparsi del codice necessario al rendering , in quanto tale dettaglio sarà di responsabilità della specifica figura geometrica! L'insegnamento sotteso al DIP, concettualmente semplice, é di non fidarsi di ciò che può cambiare, e generalmente ciò che cambia nello sviluppo di software object oriented sono classi concrete.

Un altro importante criterio di design è l'Interface Segregation Principle (ISP) secondo il quale "molte interfacce-client specifiche sono meglio di un'interfaccia generica". Una tale affermazione può sembrare estremamente sbagliata specie alla luce degli altri due principi prima enunciati. In realtà non viene scoraggiato l'uso di astrazioni bensì non si vogliono Fat Interface, vale a dire interfacce sovraccariche di responsabilità. Vediamo un piccolo esempio in Java. Supponiamo di voler modellare il fatto che una persona possa essere uno studente, un lavoratore, un atleta. Modellare la classe persona con i metodi relativi alle altre tre astrazioni renderebbe la sua interfaccia pubblica sovraccarica: non tutte le persone sono allo stesso tempo studenti, lavoratori ed atleti!

class Person
{
  public void setName(String name)
  {
    ...
  }
  
  public String getName()
  {
    ...
  }
  
  public void study()
  {
    ...
  }
  
  public void work()
  {
    ...
  }
  
  pubic void jogging()
  {
    ...
  }
}
In questo caso è meglio avere i comportamenti suddivisi in elementi differenziabili; nel nostro caso l'idioma interface di Java ci permette di definire ruoli che possono essere composti secondo gli aspetti da modellare.
public interface Worker
{
  public void work();
} 

public interface Student
{
  public void study();
}

public interface Athlete
{
  public void jogging();
}


public class AthleticStudent extends Person implements Student, Athlete
{
  ...
}

public class StudentWorker extends Person implements Studetn, Worker
{
  ...
}
È bene notare che la presenza di interfacce specifiche non significa legarsi a date implementazioni sia per la natura astratta dell'interfaccia sia per l'idea di classificazione sottesa ad essa: il contratto formalizzato da un'interfaccia riguarda la famiglia di elementi che ne rispettano i comportamenti.

L'ultimo principio che presentiamo è legato al meccanismo dell'ereditarietà, elemento distintivo del design ad oggetti. Il Liskov Substitution Principle (LSP) indica che " le sottoclassi dovrebbero essere sostituti per le classi base": in altre parole se un metodo usa un riferimento ad una classe base, in una gerarchia, deve essere possibile utilizzare una sua sottoclasse senza che si verifichino problemi o malfunzionamenti. L'idea di fondo è legata al concetto di design per contratto secondo cui un metodo può eseguire il codice correlato, se soddisfatte date precondizioni; dopo la sua esecuzione si ritroveranno soddisfate date postcondizioni. Il LSP ci indica che una classe derivata è un sostituto per la classe base se:

  • Le precondizioni dei suoi metodi non sono più forti di quelle della classe base.
  • Le postcondizioni dei suoi metodi non sono più deboli di quelle della classe base.
Un classico esempio di violazione del LSP è il dilemma del Cerchio e dell'Ellisse: un cerchio è un'ellisse? La matematica ci insegna che la risposta è affermativa se si considera il rapporto degenerativo che intercorre tra cerchi ed ellisse; ne consegue un naturale modo di modellare i due concetti per cui la classe Ellisse è superclasse della classe Cerchio, sua sottoclasse. Nel modo ad oggetti questo discorso non è più valido dato che il paradigma non è legato alla formulazione matematica delle due astrazione ma alla loro modellazione nel rapporto collaborativo con altri oggetti. In questa ottica un cerchio non è un'ellisse dato che i loro due contratti sono differenti; in particolare non vi può essere rapporto di ereditarietà visto che il contratto del cerchio, fornito di centro, vìola quella dell'ellisse, fornita di due fuochi.

Conclusioni

I concetti esposti in questo articolo possono sembrare vaghi data la loro formulazione generica. In realtà è possibile derivare linee guida pratiche utilizzabili ogni giorno. Il processo di creazione di istanze, ad esempio, può essere affrontato considerando l'OCP ed il DIP per cui è necessario nascondere la specifica definizione di una famiglia di oggetti in una FACTORY. Un altro esempio si concretizza nel Bridge pattern della G.o.F. dove l'OPC, il DIP e il LSP permettono di separare astrazioni dalle rispettive implementazioni e operare anche in modo polimorfico grazie all'ereditarietà. L'aspetto comunque più interessante di questi principi, ed in ciò esprimo una nota personale, è la loro naturalezza che, al di fuori dei necessari formalismi, è comunque riconducibile a problemi che tutti abbiamo riscontrato ed al classico e sempre utile buon senso!

Informazioni sull'autore

Stefano Fago, classe 1973. Diplomato in ragioneria, ha conseguito il Diploma di Laurea in Informatica con un progetto legato alle interfacce grafiche soft-realtime in Java. Dopo esperienze in Alcatel ed Elea, ha svolto attività di consulenza come Software Developer e Trainer alla ObjectWay S.p.A. sede di Milano. Attualmente Software Designer presso la sezione Innovazione e Attività Progettuali di BPU Banca. Appassionato del linguaggio Java e di tutte le tecnolgie Object Oriented. Polistrumentista dilettante.

È possibile consultare l'elenco degli articoli scritti da Stefano Fago.

Altri articoli sul tema Design / OOP.

Risorse

  1. Program Development in Java: Abstract, Specification, and Object-Oriented Design - Barbara Liskov & John Guttag.
Discuti sul forum   Stampa

Cosa ne pensi di questo articolo?

Discussioni

Questo articolo o l'argomento ti ha interessato? Parliamone.