Eccoci tornati nel secondo capitolo dei Design Pattern. Questa volta è il turno dei Structural Design Patterns, sono una categoria che si concentrano sulla composizione di classi e oggetti per formare strutture più complesse. Questi pattern mirano a migliorare l'efficienza e la chiarezza del sistema, permettendo di comporre oggetti in modi flessibili.
Adapter Pattern
L'Adapter Pattern è utilizzato per consentire a due interfacce incompatibili di lavorare insieme. Questo è particolarmente utile quando si utilizzano librerie o classi esterne con interfacce diverse dalla nostra applicazione. Ecco un esempio:
// Vecchia interfaccia incompatibile con il nostro codice
public interface VecchiaInterfaccia {
void operazioneVecchia();
}
// Nuova interfaccia che il nostro codice deve usare
public interface NuovaInterfaccia {
void operazioneNuova();
}
// Adapter che collega la vecchia interfaccia alla nuova
public class Adapter implements NuovaInterfaccia {
private VecchiaInterfaccia vecchiaOggetto;
public Adapter(VecchiaInterfaccia vecchiaOggetto) {
this.vecchiaOggetto = vecchiaOggetto;
}
@Override
public void operazioneNuova() {
vecchiaOggetto.operazioneVecchia();
}
}
Obiettivo:
Immagina di sviluppare un videogioco in cui hai una libreria di gestione del suono che è progettata per funzionare con una determinata interfaccia audio. Tuttavia, la libreria di gestione del suono utilizza un'interfaccia audio vecchia e obsoleta, ma tu vuoi utilizzare una nuova e più avanzata libreria audio con un'interfaccia diversa. Il problema è che le due interfacce audio sono incompatibili, ma desideri comunque utilizzare la nuova libreria audio senza dover riscrivere l'intero codice del gioco.
Soluzione:
Per risolvere questo problema, abbiamo bisogno di diversi step:
- Definisci un'interfaccia InterfacciaAudio che rappresenta l'interfaccia audio desiderata che vuoi utilizzare nel tuo gioco. Questa interfaccia sarà incompatibile con la vecchia libreria audio.
public interface InterfacciaAudio {
void playSound(String sound);
void stopSound();
}
- Implementa una classe chiamata LibreriaAudioNuova che rappresenta la tua nuova libreria audio che segue l'interfaccia desiderata
public class LibreriaAudioNuova implements InterfacciaAudio {
// Implementa i metodi dell'interfaccia InterfacciaAudio
public void playSound(String sound) {
System.out.println("Suono riprodotto: " + sound);
}
public void stopSound() {
System.out.println("Riproduzione audio interrotta.");
}
}
- Ora, per utilizzare la tua nuova libreria audio nel gioco, crea un adapter che si adatta alla vecchia libreria audio utilizzando l'interfaccia desiderata.
public class AdapterAudio implements InterfacciaAudio {
private LibreriaAudioVecchia libreriaAudioVecchia;
public AdapterAudio(LibreriaAudioVecchia libreriaAudioVecchia) {
this.libreriaAudioVecchia = libreriaAudioVecchia;
}
public void playSound(String sound) {
// Adatta il play dell'interfaccia vecchia all'interfaccia desiderata
libreriaAudioVecchia.play(sound);
}
public void stopSound() {
// Adatta lo stop dell'interfaccia vecchia all'interfaccia desiderata
libreriaAudioVecchia.stop();
}
}
- Ora puoi utilizzare il tuo adapter per utilizzare la nuova libreria audio nel gioco, anche se la vecchia libreria utilizza un'interfaccia diversa:
// Creazione dell'adapter per utilizzare la nuova libreria audio
LibreriaAudioVecchia libreriaAudioVecchia = new LibreriaAudioVecchia();
InterfacciaAudio adapter = new AdapterAudio(libreriaAudioVecchia);
// Utilizzo dell'interfaccia desiderata per riprodurre un suono
adapter.playSound("Effetto sonoro del gioco");
// Interrompi la riproduzione audio
adapter.stopSound();
In questo modo, hai risolto il problema di incompatibilità tra la vecchia e la nuova libreria audio nel tuo gioco utilizzando l'Adapter Pattern. L'adapter si adatta all'interfaccia audio vecchia e permette di utilizzare la nuova libreria audio senza dover modificare il resto del codice del gioco.
Decorator Pattern
Il Decorator Pattern è utilizzato per aggiungere funzionalità aggiuntive a oggetti senza modificarne la struttura. Questo è utile quando si desidera estendere le capacità di una classe senza creare sottoclassi. Ecco un esempio:
// Interfaccia Component base
public interface Component {
void operazione();
}
// Implementazione concreta di Component
public class ComponentConcreto implements Component {
@Override
public void operazione() {
System.out.println("Operazione di ComponentConcreto");
}
}
// Decorator base
public abstract class Decorator implements Component {
protected Component componente;
public Decorator(Component componente) {
this.componente = componente;
}
@Override
public void operazione() {
componente.operazione();
}
}
// Decorator concreto
public class DecoratorConcreto extends Decorator {
public DecoratorConcreto(Component componente) {
super(componente);
}
@Override
public void operazione() {
super.operazione();
aggiuntaAggiuntiva();
}
private void aggiuntaAggiuntiva() {
System.out.println("Funzionalità aggiuntiva di DecoratorConcreto");
}
}
Obiettivo:
Immagina di sviluppare un videogioco in cui i personaggi possono acquisire abilità speciali come invisibilità, velocità aumentata e scudi protettivi. Vuoi poter aggiungere queste abilità ai personaggi in modo dinamico durante il gioco senza modificare il codice delle classi base dei personaggi.
Soluzione:
Il Decorator Pattern permette di aggiungere comportamenti aggiuntivi agli oggetti in modo flessibile. Ecco come si può implementare questo pattern:
- Definisci un'interfaccia comune per i personaggi del gioco
// Interfaccia per i personaggi del gioco
public interface Character {
void describe();
}
- Implementa una classe concreta per un personaggio base.
// Implementazione concreta di un personaggio base
public class BaseCharacter implements Character {
@Override
public void describe() {
System.out.println("Sono un personaggio base.");
}
}
- Crea una classe astratta CharacterDecorator che implementa l'interfaccia Character e contiene un riferimento a un oggetto Character.
// Classe astratta Decorator che implementa l'interfaccia Character
public abstract class CharacterDecorator implements Character {
protected Character decoratedCharacter;
public CharacterDecorator(Character decoratedCharacter) {
this.decoratedCharacter = decoratedCharacter;
}
@Override
public void describe() {
decoratedCharacter.describe();
}
}
- Implementa i decorator concreti per le abilità speciali.
// Decorator per l'abilità di invisibilità
public class InvisibilityDecorator extends CharacterDecorator {
public InvisibilityDecorator(Character decoratedCharacter) {
super(decoratedCharacter);
}
@Override
public void describe() {
decoratedCharacter.describe();
System.out.println("Ho l'abilità di invisibilità.");
}
}
// Decorator per l'abilità di velocità aumentata
public class SpeedDecorator extends CharacterDecorator {
public SpeedDecorator(Character decoratedCharacter) {
super(decoratedCharacter);
}
@Override
public void describe() {
decoratedCharacter.describe();
System.out.println("Ho l'abilità di velocità aumentata.");
}
}
// Decorator per l'abilità di scudo protettivo
public class ShieldDecorator extends CharacterDecorator {
public ShieldDecorator(Character decoratedCharacter) {
super(decoratedCharacter);
}
@Override
public void describe() {
decoratedCharacter.describe();
System.out.println("Ho l'abilità di scudo protettivo.");
}
}
- Utilizza i decorator per aggiungere abilità ai personaggi durante il gioco.
public class Game {
public static void main(String[] args) {
// Crea un personaggio base
Character baseCharacter = new BaseCharacter();
// Aggiungi l'abilità di invisibilità al personaggio
Character invisibleCharacter = new InvisibilityDecorator(baseCharacter);
// Aggiungi l'abilità di velocità aumentata al personaggio invisibile
Character speedyInvisibleCharacter = new SpeedDecorator(invisibleCharacter);
// Aggiungi l'abilità di scudo protettivo al personaggio con velocità aumentata
Character shieldedSpeedyInvisibleCharacter = new ShieldDecorator(speedyInvisibleCharacter);
// Descrivi il personaggio finale con tutte le abilità
shieldedSpeedyInvisibleCharacter.describe();
}
}
OUTPUT
Sono un personaggio base.
Ho l'abilità di invisibilità.
Ho l'abilità di velocità aumentata.
Ho l'abilità di scudo protettivo.
Composite Pattern
Il Composite Pattern è utilizzato per creare strutture ad albero e trattare oggetti singoli e composizioni di oggetti in modo uniforme. Questo è utile quando si vuole rappresentare una gerarchia di oggetti in un modo che semplifica l'interazione con essi. Ecco un esempio:
// Interfaccia Component per oggetti singoli e composizioni
public interface Component {
void operazione();
}
// Implementazione concreta di Component per oggetti singoli
public class Foglia implements Component {
@Override
public void operazione() {
System.out.println("Operazione di Foglia");
}
}
// Implementazione concreta di Component per composizioni
public class Composite implements Component {
private List<Component> componenti = new ArrayList<>();
public void aggiungi(Component componente) {
componenti.add(componente);
}
@Override
public void operazione() {
System.out.println("Operazione di Composite");
for (Component componente : componenti) {
componente.operazione();
}
}
}
Obiettivo:
Immagina di sviluppare un videogioco in cui hai diversi tipi di unità, come singole unità (es. soldati) e gruppi di unità (es. plotoni, battaglioni). Vuoi poter trattare sia le singole unità che i gruppi di unità in modo uniforme, ad esempio per ordinarli di attaccare o difendere.
Soluzione:
Il Composite Pattern permette di creare una struttura ad albero di oggetti e di trattare gli oggetti singoli e le composizioni di oggetti in modo uniforme. Ecco come si può implementare questo pattern:
- Definisci un'interfaccia comune per le unità del gioco.
// Interfaccia per le unità del gioco
public interface Unita {
void attacca();
void difendi();
}
- Implementa una classe concreta per le unità singole
// Implementazione concreta di una singola unità (Soldato)
public class Soldato implements Unita {
private String nome;
public Soldato(String nome) {
this.nome = nome;
}
@Override
public void attacca() {
System.out.println(nome + " sta attaccando.");
}
@Override
public void difendi() {
System.out.println(nome + " sta difendendo.");
}
}
- Implementa una classe concreta per le unità composite che possono contenere altre unità.
// Implementazione concreta di un gruppo di unità (Composite)
import java.util.ArrayList;
import java.util.List;
public class UnitaComposite implements Unita {
private List<Unita> unita = new ArrayList<>();
public void aggiungiUnita(Unita unita) {
this.unita.add(unita);
}
public void rimuoviUnita(Unita unita) {
this.unita.remove(unita);
}
@Override
public void attacca() {
for (Unita unita : unita) {
unita.attacca();
}
}
@Override
public void difendi() {
for (Unita unita : unita) {
unita.difendi();
}
}
}
- Utilizza il Composite Pattern per creare una gerarchia di unità nel gioco.
public class Gioco {
public static void main(String[] args) {
// Crea singole unità
Unita soldato1 = new Soldato("Soldato 1");
Unita soldato2 = new Soldato("Soldato 2");
Unita soldato3 = new Soldato("Soldato 3");
// Crea un gruppo di unità
UnitaComposite plotone = new UnitaComposite();
plotone.aggiungiUnita(soldato1);
plotone.aggiungiUnita(soldato2);
// Crea un battaglione che include il plotone e un altro soldato
UnitaComposite battaglione = new UnitaComposite();
battaglione.aggiungiUnita(plotone);
battaglione.aggiungiUnita(soldato3);
// Ordina al battaglione di attaccare
battaglione.attacca();
// Ordina al battaglione di difendere
battaglione.difendi();
}
}
OUTPUT
Soldato 1 sta attaccando.
Soldato 2 sta attaccando.
Soldato 3 sta attaccando.
Soldato 1 sta difendendo.
Soldato 2 sta difendendo.
Soldato 3 sta difendendo.
Conclusione
I Structural Design Patterns forniscono soluzioni per la composizione di oggetti in modo flessibile, il collegamento di interfacce incompatibili e l'estensione delle funzionalità degli oggetti. Utilizzando questi pattern, è possibile progettare applicazioni più modulari e manutenibili. Scegli il pattern più adatto alle esigenze specifiche del tuo progetto per migliorare la struttura e l'efficienza del codice.
Commenti
Posta un commento