Structural Patterns

Adapter

Il pattern Adapter prevede che una classe o interfaccia venga utilizzata per adattare due interfacce “incompatibili”. Un caso molto comune di utilizzo dell’adapter è quando si decide (o si deve) utilizzare una classe di “libreria” la cui interfaccia non è compatibile con il codice già scritto.

Supponiamo che un software gestionale acceda ai dati mediante un database e che per fare questo usi una libreria con i seguenti metodi

public class DatabaseAccess {
    public Record[] query(String sqlQuery);
}

il codice client potrebbe utilizzare un’istanza di questa classe nel seguente modo

// ...
String dbAddress = "db.domain.org";
DatabaseAccess db = connect(dbAddress);
String sql = "SELECT * from PRODUCTS WHERE price < 100;";
for (Record r : db.query(sql)) {
    System.out.println(r);
}
// ...

Ad un certo punto potrebbe essere necessario cambiare la libreria di accesso al database la quale potrebbe avere un’interfaccia un po’ diversa, ad esempio

// ...
public class NewDatabaseAccess {
    public void submit(String sqlQuery);
    public List<Record> exec() throws DBError;
}

Ovviamente per adattare il codice alla nuova classe si può procedere a cambiare tutti i punti in cui si accede al database, in alternativa, tuttavia si può creare una classe DatabaseAccess con lo stesso nome della libreria originale e con la stessa interfaccia. La differenza è che l’implementazione sarà “custom” e si occuperà di accedere alla nuova libreria.

// Questa è una classe "custom"
public class DatabaseAccess {
    private NewDatabaseAccess db;
    // ...
    public Record[] query(String sqlQuery) {
        List<Record> results = null;
        db.submit(sqlQuery);
        try {
            results = db.exec();
        } catch(DBError r) {
            // ...
        }
        if (results != null) {
            Record[] out = new Record[results.size()];
            results.toArray(out);
            return out;
        }
        return null;
    }
}

Una volta creato questo metodo query(), l’intero codice client può continuare a funzionare senza che vi sia necessità di cambiare una sola riga, semplicemente la classe DatabaseAccess ora fa riferimento alla nostra classe custom che, internamente, utilizza la “nuova” classe NewDatabaseAccess.

La classe DatabaseAccess così creata viene classe adapter perché funge da “adattatore” tra il codice client e la nuova classe NewDatabaseAccess, un altro nome dell’adapter è wrapper in quanto la classe “avvolge” al suo interno i dettagli della nuova classe.

Facade

Il pattern facade prevede la creazione di un’interfaccia unica componendo diverse interfacce di un sistema complesso. In questo caso non bisogna intendere il termine interfaccia come un interface Java, bensì nel senso di una API (Application Programming Interface).

Supponiamo, ad esempio, di progettare un gioco in cui un personaggio virtuale interagisce con gli elementi del mondo virtuale in cui si muove. Questi elementi possono essere diversi: la mappa, altri giocatori, l’inventario, … Dal punto di vista della classe Player, quindi, potrebbe essere necessario interagire con altre classi: Map, Player (altri giocatori), Inventory, … In questo caso può risultare più comodo avere un interfaccia World che permette l’interazione con un i vari altri oggetti attraverso un’unica facciata.

Nell’esempio che segue vediamo una classe World offre metodi per l’interazione con varie parti del gioco. L’interazione (ad esempio fight) può coinvolgere diversi aspetti e diverse altre classi, dal punto di vista del codice client, questa complessità non è visibile poiché nascosta dalla classe World che è, in questo esempio, la classe facade (facciata).

public class World {
    private Player[] players;
    private Map map;

    public void fight(Player attack, Player defense) {
        // Perform a fight between two players
    }
    public void discoverMap(int x, int y) {
        // Discover some new parts of the world
    }

    // ...
}

Proxy

Il proxy pattern prevede una classe che possa fungere da “segnaposto” per un’altra classe. Questo può essere utile, ad esempio, per creare la classe da sostituire c’è bisogno di tanto tempo oppure di informazione non ancora disponibile.

Nelle interfacce grafiche, ad esempio, l’operazione di caricamento delle risorse (da remoto o da file) può richiedere del tempo, durante questo periodo l’interfaccia potrebbe essere inutilizzabile o non completa poiché in attesa della risorsa mancante. Anziché bloccare l’esecuzione fino al completamento della richiesta onerosa, si può utilizzare una classe proxy, veloce di istanziare, che prenda il posto della classe la cui istanaziazione non è ancora terminata.

The following code shows a ProxyResource class and a TimeConsumiResource class both extending the same Resource class. In the constructor of ProxyResource a download is started for the original resource, while it downloads, the instance of ProxyResource is served. Whenever the original resource is ready, it is used when needed.

public class ProxyResource extends Resource {
    private Resource res;
    private TimeConsumingResource original;

    public ProxyResource(String url) {
        original = new TimeConsumingResource(url);
        res = this;
    }
    Resource get() {
        if (original.isReady()) {
            res = original;
        }
        return res;
    }
}

public class TimeConsumingResource extends Resource {
    public TimeConsumingResource(String url) {
        // starti downloading  resource
    }

    public boolean isReady() {
        return false;
    }
}
  • Michele Schimd © 2024
  • Ultimo aggiornamento: 17/02/2024
  • Materiale di studio e di esercizio per gli alunni dello Zuccante.

Creative Commons License