Abbiamo già esaminato l'uso di un oggetto singleton, ma potresti non renderti ancora conto che questa strategia è un modello di progettazione e uno dei più utilizzati.

In effetti, ci sono molti di questi modelli e possono essere classificati in base al loro scopo specifico.

Classificazione del modello

Tipo di motivo Applicazione
Creativo Un tipo che risolve il problema della creazione dell'oggetto
Strutturale Pattern che ci permettono di costruire una gerarchia di classi corretta ed estensibile nella nostra architettura
comportamentale Questo gruppo di modelli facilita l'interazione sicura e conveniente tra gli oggetti in un programma.

Tipicamente, un modello è caratterizzato dal problema che risolve. Diamo un'occhiata ad alcuni schemi che incontriamo più spesso quando lavoriamo con Java:

Modello Scopo
Singleton Conosciamo già questo modello: lo usiamo per creare e accedere a una classe che non può avere più di un'istanza.
Iteratore Conosciamo anche questo. Sappiamo che questo modello ci permette di iterare su un oggetto collezione senza rivelare la sua rappresentazione interna. Viene utilizzato con le raccolte.
Adattatore Questo modello collega oggetti incompatibili in modo che possano lavorare insieme. Penso che il nome del modello dell'adattatore ti aiuti a immaginare esattamente cosa fa. Ecco un semplice esempio tratto dalla vita reale: un adattatore USB per una presa a muro.
Metodo del modello

Un modello di programmazione comportamentale che risolve il problema dell'integrazione e consente di modificare i passaggi algoritmici senza modificare la struttura di un algoritmo.

Immagina di avere un algoritmo di assemblaggio di automobili sotto forma di una sequenza di fasi di assemblaggio:

Telaio -> Carrozzeria -> Motore -> Interni cabina

Se inseriamo un telaio rinforzato, un motore più potente o un interno con illuminazione aggiuntiva, non dobbiamo cambiare l'algoritmo e la sequenza astratta rimane la stessa.

Decoratore Questo modello crea wrapper per gli oggetti per fornire loro funzionalità utili. Lo considereremo come parte di questo articolo.

In Java.io, le seguenti classi implementano modelli:

Modello Dove viene utilizzato in java.io
Adattatore
Metodo del modello
Decoratore

Modello decoratore

Immaginiamo di descrivere un modello per il design di una casa.

In generale, l'approccio si presenta così:

Inizialmente, abbiamo una scelta di diversi tipi di case. La configurazione minima è un piano con tetto. Quindi utilizziamo tutti i tipi di decoratori per modificare parametri aggiuntivi, che naturalmente influiscono sul prezzo della casa.

Creiamo una classe House astratta:


public abstract class House {
	String info;
 
	public String getInfo() {
    	return info;
	}
 
	public abstract int getPrice();
}
    

Qui abbiamo 2 metodi:

  • getInfo() restituisce informazioni sul nome e le caratteristiche della nostra casa;
  • getPrice() restituisce il prezzo della configurazione attuale della casa.

Abbiamo anche implementazioni standard di House - mattoni e legno:


public class BrickHouse extends House {
 
	public BrickHouse() {
    	info = "Brick House";
	}
 
	@Override
	public int getPrice() {
    	return 20_000;
	}
}
 
public class WoodenHouse extends House {
 
	public WoodenHouse() {
    	info = "Wooden House";
	}
 
	@Override
	public int getPrice() {
    	return 25_000;
	}
}
    

Entrambe le classi ereditano la classe Casa e sovrascrivono il suo metodo di prezzo, impostando un prezzo personalizzato per una casa standard. Impostiamo il nome nel costruttore.

Successivamente, dobbiamo scrivere classi di decoratori. Queste classi erediteranno anche la classe House . Per fare ciò, creiamo una classe decoratore astratta.

È qui che inseriremo una logica aggiuntiva per modificare un oggetto. Inizialmente, non ci sarà alcuna logica aggiuntiva e la classe astratta sarà vuota.


abstract class HouseDecorator extends House {
}
    

Successivamente, creiamo implementazioni del decoratore. Creeremo diverse classi che ci consentiranno di aggiungere funzionalità aggiuntive alla casa:


public class SecondFloor extends HouseDecorator {
	House house;
 
	public SecondFloor(House house) {
    	this.house = house;
	}
 
	@Override
	public int getPrice() {
    	return house.getPrice() + 20_000;
	}
 
	@Override
	public String getInfo() {
    	return house.getInfo() + " + second floor";
	}
}
    
Un decoratore che aggiunge un secondo piano alla nostra casa

Il costruttore decoratore accetta una casa che "decoreremo", cioè aggiungeremo modifiche. E sovrascriviamo i metodi getPrice() e getInfo() , restituendo informazioni sulla nuova casa aggiornata in base a quella vecchia.


public class Garage extends HouseDecorator {
 
	House house;
	public Garage(House house) {
    	this.house = house;
	}
 
	@Override
	public int getPrice() {
    	return house.getPrice() + 5_000;
	}
 
	@Override
	public String getInfo() {
    	return house.getInfo() + " + garage";
	}
}
    
Un decoratore che aggiunge un garage a casa nostra

Ora possiamo aggiornare la nostra casa con i decoratori. Per fare questo, dobbiamo creare una casa:


House brickHouse = new BrickHouse();
    

Successivamente, impostiamo il nostrocasavariabile uguale ad un nuovo decoratore, passando in casa nostra:


brickHouse = new SecondFloor(brickHouse); 
    

Nostrocasavariabile ora è una casa con un secondo piano.

Diamo un'occhiata ai casi d'uso che coinvolgono i decoratori:

Codice di esempio Produzione

House brickHouse = new BrickHouse(); 

  System.out.println(brickHouse.getInfo());
  System.out.println(brickHouse.getPrice());
                    

Casa di mattoni

20000


House brickHouse = new BrickHouse(); 

  brickHouse = new SecondFloor(brickHouse); 

  System.out.println(brickHouse.getInfo());
  System.out.println(brickHouse.getPrice());
                    

Casa in mattoni + secondo piano

40000


House brickHouse = new BrickHouse();
 

  brickHouse = new SecondFloor(brickHouse);
  brickHouse = new Garage(brickHouse);

  System.out.println(brickHouse.getInfo());
  System.out.println(brickHouse.getPrice());
                    

Casa in muratura + secondo piano + garage

45000


House woodenHouse = new SecondFloor(new Garage(new WoodenHouse())); 

  System.out.println(woodenHouse.getInfo());
  System.out.println(woodenHouse.getPrice());
                    

Casa in legno + garage + secondo piano

50000


House woodenHouse = new WoodenHouse(); 

  House woodenHouseWithGarage = new Garage(woodenHouse);

  System.out.println(woodenHouse.getInfo());
  System.out.println(woodenHouse.getPrice());

  System.out.println(woodenHouseWithGarage.getInfo());
  System.out.println(woodenHouseWithGarage.getPrice());
                    

Casa di legno

25000

Casa in legno + garage

30000

Questo esempio illustra il vantaggio dell'aggiornamento di un oggetto con un decoratore. Quindi non abbiamo cambiato ilcasa di legnooggetto stesso, ma invece ha creato un nuovo oggetto basato su quello vecchio. Qui possiamo vedere che i vantaggi hanno degli svantaggi: creiamo ogni volta un nuovo oggetto in memoria, aumentando il consumo di memoria.

Guarda questo diagramma UML del nostro programma:

Un decoratore ha un'implementazione super semplice e cambia dinamicamente gli oggetti, aggiornandoli. I decoratori possono essere riconosciuti dai loro costruttori, che prendono come parametri oggetti dello stesso tipo astratto o interfaccia della classe corrente. In Java, questo modello è ampiamente utilizzato nelle classi di I/O.

Ad esempio, come abbiamo già notato, tutte le sottoclassi di java.io.InputStream , OutputStream , Reader e Writer hanno un costruttore che accetta oggetti delle stesse classi.