#3 Design Patterns - "Behavioral Design Patterns: Il Segreto per un Software Intelligente


 

Nel campo dello sviluppo software, i design pattern rappresentano soluzioni consolidate a problemi comuni che emergono durante la progettazione del software. I design pattern si suddividono in tre categorie principali: creazionali, strutturali e comportamentali. In questo articolo ci concentreremo sui design pattern comportamentali, che riguardano le interazioni e le responsabilità tra gli oggetti.

Cos'è un Design Pattern Comportamentale?

I design pattern comportamentali si focalizzano su come gli oggetti interagiscono tra loro e su come vengono distribuite le responsabilità tra di essi. Questi pattern aiutano a definire le modalità di comunicazione tra gli oggetti e a migliorare la flessibilità e l'estensibilità del codice. Alcuni dei design pattern comportamentali più noti includono il Pattern Strategy, il Pattern Observer, e il Pattern Command.

Principali Design Pattern Comportamentali

Obiettivo/Problema: Nel tuo videogioco, vuoi gestire una serie di comandi di input da parte del giocatore, come movimento, attacco e difesa. Ogni comando può essere gestito da un diverso handler e vuoi che il sistema di gestione possa passare il comando attraverso una catena di handler finché non viene gestito.

Soluzione con il Chain of Responsibility Pattern:

  • Definizione dei comandi e degli handler: 

// Comando base
public abstract class CommandHandler {
    protected CommandHandler nextHandler;

    public void setNextHandler(CommandHandler nextHandler) {
        this.nextHandler = nextHandler;
    }

    public abstract void handleCommand(String command);
}

// Handler per il comando di movimento
public class MovementHandler extends CommandHandler {
    @Override
    public void handleCommand(String command) {
        if (command.equals("MOVE")) {
            System.out.println("Il personaggio si sta muovendo.");
        } else if (nextHandler != null) {
            nextHandler.handleCommand(command);
        }
    }
}

// Handler per il comando di attacco
public class AttackHandler extends CommandHandler {
    @Override
    public void handleCommand(String command) {
        if (command.equals("ATTACK")) {
            System.out.println("Il personaggio sta attaccando.");
        } else if (nextHandler != null) {
            nextHandler.handleCommand(command);
        }
    }
}

// Handler per il comando di difesa
public class DefenseHandler extends CommandHandler {
    @Override
    public void handleCommand(String command) {
        if (command.equals("DEFEND")) {
            System.out.println("Il personaggio sta difendendo.");
        } else if (nextHandler != null) {
            nextHandler.handleCommand(command);
        }
    }
}

  • Utilizzo del Chain of Responsibility:

public class Main {
    public static void main(String[] args) {
        CommandHandler movementHandler = new MovementHandler();
        CommandHandler attackHandler = new AttackHandler();
        CommandHandler defenseHandler = new DefenseHandler();

        movementHandler.setNextHandler(attackHandler);
        attackHandler.setNextHandler(defenseHandler);

        // Test dei comandi
        movementHandler.handleCommand("MOVE");
        movementHandler.handleCommand("ATTACK");
        movementHandler.handleCommand("DEFEND");
        movementHandler.handleCommand("JUMP");  // Non gestito
    }
}

Observer Pattern

Obiettivo/Problema: Nel tuo videogioco, vuoi che i vari elementi del gioco, come il punteggio e la salute, siano aggiornati e visualizzati ogni volta che cambiano. Ogni volta che il punteggio del giocatore cambia, i display del punteggio e della salute devono essere aggiornati di conseguenza.

Soluzione con l'Observer Pattern:
  • Definizione delle interfacce e delle classi
import java.util.ArrayList;
import java.util.List;

// Interfaccia Observer
public interface Observer {
    void update(int score);
}

// Interfaccia Subject
public interface Subject {
    void addObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

// Implementazione concreta di Subject
public class GameScore implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private int score;

    public void setScore(int score) {
        this.score = score;
        notifyObservers();
    }

    @Override
    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(score);
        }
    }
}

// Implementazione concreta di Observer
public class ScoreDisplay implements Observer {
    @Override
    public void update(int score) {
        System.out.println("Display del punteggio aggiornato a: " + score);
    }
}

public class HealthDisplay implements Observer {
    @Override
    public void update(int score) {
        System.out.println("Display della salute aggiornato in base al punteggio: " + score);
    }
}

  • Utilizzo dell'Observer Pattern:

public class Main {
    public static void main(String[] args) {
        GameScore gameScore = new GameScore();
        Observer scoreDisplay = new ScoreDisplay();
        Observer healthDisplay = new HealthDisplay();

        gameScore.addObserver(scoreDisplay);
        gameScore.addObserver(healthDisplay);

        gameScore.setScore(100);
        gameScore.setScore(200);
    }
}

Strategy Pattern

Obiettivo/Problema: Nel tuo videogioco, hai diversi tipi di armi, ognuna con un proprio comportamento di attacco. Vuoi permettere ai giocatori di cambiare l'arma durante il gioco e modificare il comportamento di attacco di conseguenza senza modificare il codice della classe del personaggio.

Soluzione con il Strategy Pattern:

  • Definizione delle strategie e del contesto:

// Interfaccia Strategy
public interface AttackStrategy {
    void attack();
}

// Implementazione concreta della Strategy per un'arma a distanza
public class RangedAttack implements AttackStrategy {
    @Override
    public void attack() {
        System.out.println("Attacco a distanza con frecce.");
    }
}

// Implementazione concreta della Strategy per un'arma corpo a corpo
public class MeleeAttack implements AttackStrategy {
    @Override
    public void attack() {
        System.out.println("Attacco corpo a corpo con spada.");
    }
}

// Contesto che utilizza la Strategy
public class Character {
    private AttackStrategy attackStrategy;

    public void setAttackStrategy(AttackStrategy attackStrategy) {
        this.attackStrategy = attackStrategy;
    }

    public void performAttack() {
        attackStrategy.attack();
    }
}
  • Utilizzo del Strategy Pattern:
public class Main {
    public static void main(String[] args) {
        Character character = new Character();

        // Utilizza l'arma a distanza
        character.setAttackStrategy(new RangedAttack());
        character.performAttack();

        // Cambia a un'arma corpo a corpo
        character.setAttackStrategy(new MeleeAttack());
        character.performAttack();
    }
}

Command Pattern

Obiettivo/Problema: Nel tuo videogioco, vuoi implementare un sistema di comandi che permetta al giocatore di eseguire azioni come saltare, attaccare e muoversi. Ogni comando può essere eseguito in un momento successivo e supportare l'operazione di undo.

Soluzione con il Command Pattern:

  • Definizione dei comandi e del ricevitore:

// Interfaccia Command
public interface Command {
    void execute();
    void undo();
}

// Comando concreto per saltare
public class JumpCommand implements Command {
    private Character character;

    public JumpCommand(Character character) {
        this.character = character;
    }

    @Override
    public void execute() {
        character.jump();
    }

    @Override
    public void undo() {
        character.land();
    }
}

// Comando concreto per attaccare
public class AttackCommand implements Command {
    private Character character;

    public AttackCommand(Character character) {
        this.character = character;
    }

    @Override
    public void execute() {
        character.attack();
    }

    @Override
    public void undo() {
        character.stopAttacking();
    }
}

// Ricevitore delle azioni
public class Character {
    public void jump() {
        System.out.println("Il personaggio salta.");
    }

    public void land() {
        System.out.println("Il personaggio atterra.");
    }

    public void attack() {
        System.out.println("Il personaggio attacca.");
    }

    public void stopAttacking() {
        System.out.println("Il personaggio smette di attaccare.");
    }
}

// Invoker che gestisce i comandi
public class GameController {
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public void pressButton() {
        command.execute();
    }

    public void pressUndo() {
        command.undo();
    }
}

  • Utilizzo del Command Pattern:

public class Main {
    public static void main(String[] args) {
        Character character = new Character();
        Command jumpCommand = new JumpCommand(character);
        Command attackCommand = new AttackCommand(character);

        GameController controller = new GameController();

        // Esegui e annulla il comando di salto
        controller.setCommand(jumpCommand);
        controller.pressButton();
        controller.pressUndo();

        // Esegui e annulla il comando di attacco
        controller.setCommand(attackCommand);
        controller.pressButton();
        controller.pressUndo();
    }
}

Conclusione

I Behavioral Design Patterns offrono soluzioni eleganti per gestire l'interazione e il comportamento degli oggetti nei tuoi videogiochi. Utilizzando i pattern Chain of Responsibility, Observer, Strategy e Command, puoi creare sistemi di gioco flessibili e scalabili, migliorando l'organizzazione del codice e la manutenzione. Ogni pattern risolve specifiche sfide legate al comportamento e alla comunicazione degli oggetti, rendendo le tue applicazioni di gioco più robuste e facili da adattare.

Commenti

Post popolari in questo blog

#4 Space Invaders - Vola navicella....Vola - Unity Tutorial

#1 Design Patterns - Creational Design Patterns: Il Potere di Creare Oggetti con Stile

Inbox & Outbox pattern - La consegna dei messaggi ai tempi moderni