Esempio di pattern di progettazione Decorator in Java

Il pattern di progettazione Decorator viene utilizzato per modificare la funzionalità di un oggetto durante l’esecuzione. Allo stesso tempo, le altre istanze della stessa classe non vengono influenzate da questa modifica, quindi ogni oggetto individuale ottiene il comportamento modificato. Il pattern di progettazione Decorator è uno dei pattern di progettazione strutturali (come Pattern Adapter, Pattern Bridge, Pattern Composite) e utilizza classi astratte o interfacce con composizione per implementarlo.

Pattern Decorator

Utilizziamo l’ereditarietà o la composizione per estendere il comportamento di un oggetto, ma ciò avviene durante il tempo di compilazione ed è applicabile a tutte le istanze della classe. Non possiamo aggiungere nuove funzionalità o rimuovere alcun comportamento esistente durante l’esecuzione – è qui che entra in gioco il pattern Decorator. Supponiamo di voler implementare diversi tipi di auto – possiamo creare un’interfaccia Car per definire il metodo di assemblaggio e quindi possiamo avere un’auto di base, che possiamo estendere ulteriormente a un’auto sportiva e un’auto di lusso. La gerarchia di implementazione avrà un aspetto simile all’immagine sottostante. Ma se vogliamo ottenere un’auto durante l’esecuzione che abbia entrambe le caratteristiche dell’auto sportiva e dell’auto di lusso, l’implementazione diventa complessa e, se vogliamo specificare quali caratteristiche dovrebbero essere aggiunte per prime, diventa ancora più complessa. Ora immagina se avessimo dieci tipi diversi di auto, la logica di implementazione utilizzando l’ereditarietà e la composizione sarebbe impossibile da gestire. Per risolvere questo tipo di situazione di programmazione, applichiamo il pattern decoratore in Java. Abbiamo bisogno dei seguenti tipi per implementare il design pattern del decoratore.

  1. Interfaccia del componente – L’interfaccia o classe astratta che definisce i metodi che saranno implementati. Nel nostro caso Car sarà l’interfaccia del componente.

    pacchetto com.journaldev.design.decorator;
    
    public interface Car {
    
    	public void assemble();
    }
    
  2. Implementazione del componente – L’implementazione di base dell’interfaccia del componente. Possiamo avere la classe BasicCar come implementazione del nostro componente.

    pacchetto com.journaldev.design.decorator;
    
    public class BasicCar implements Car {
    
    	@Override
    	public void assemble() {
    		System.out.print("Auto di base.");
    	}
    
    }
    
  3. Decorator – La classe Decorator implementa l’interfaccia del componente ed ha una relazione HAS-A con l’interfaccia del componente. La variabile del componente dovrebbe essere accessibile alle classi decorator figlie, quindi renderemo questa variabile protetta.

    package com.journaldev.design.decorator;
    
    public class CarDecorator implements Car {
    
    	protected Car car;
    	
    	public CarDecorator(Car c){
    		this.car=c;
    	}
    	
    	@Override
    	public void assemble() {
    		this.car.assemble();
    	}
    
    }
    
  4. Concrete Decorators – Estendendo la funzionalità di base del decorator e modificando il comportamento del componente di conseguenza. Possiamo avere classi decorator concrete come LuxuryCar e SportsCar.

    package com.journaldev.design.decorator;
    
    public class SportsCar extends CarDecorator {
    
    	public SportsCar(Car c) {
    		super(c);
    	}
    
    	@Override
    	public void assemble(){
    		super.assemble();
    		System.out.print(" Aggiunta delle caratteristiche della macchina sportiva.");
    	}
    }
    
    package com.journaldev.design.decorator;
    
    public class LuxuryCar extends CarDecorator {
    
    	public LuxuryCar(Car c) {
    		super(c);
    	}
    	
    	@Override
    	public void assemble(){
    		super.assemble();
    		System.out.print(" Aggiunta delle caratteristiche della macchina di lusso.");
    	}
    }
    

Modello di progettazione Decorator – Diagramma delle classi

Programma di test del modello di progettazione Decorator

package com.journaldev.design.test;

import com.journaldev.design.decorator.BasicCar;
import com.journaldev.design.decorator.Car;
import com.journaldev.design.decorator.LuxuryCar;
import com.journaldev.design.decorator.SportsCar;

public class DecoratorPatternTest {

	public static void main(String[] args) {
		Car sportsCar = new SportsCar(new BasicCar());
		sportsCar.assemble();
		System.out.println("\n*****");
		
		Car sportsLuxuryCar = new SportsCar(new LuxuryCar(new BasicCar()));
		sportsLuxuryCar.assemble();
	}

}

Si noti che il programma client può creare diversi tipi di oggetti in fase di esecuzione e può specificare anche l’ordine di esecuzione. L’output del programma di test sopra è:

Basic Car. Adding features of Sports Car.
*****
Basic Car. Adding features of Luxury Car. Adding features of Sports Car.

Punti importanti del modello di progettazione Decorator

  • Il modello di progettazione Decorator è utile per fornire capacità di modifica in fase di esecuzione e quindi è più flessibile. È facile da mantenere ed estendere quando il numero di scelte è maggiore.
  • Lo svantaggio del modello di progettazione Decorator è che utilizza molti oggetti simili (decoratori).
  • Il pattern Decorator viene utilizzato molto nelle classi Java IO, come FileReader, BufferedReader ecc.

Source:
https://www.digitalocean.com/community/tutorials/decorator-design-pattern-in-java-example