State Pattern (padrão de desenho)/Exercício 01: Semáforo

From Wiki**3

< State Pattern (padrão de desenho)

Problema

Um semáforo tem um temporizador que emite impulsos a intervalos regulares. Estes impulsos chegam ao controlador do semáforo sob a forma de chamadas ao método tick. Em funcionamento normal, um tick faz com que o semáforo passe da cor vermelha para a verde, da verde para a amarela e da amarela para a vermelha, reiniciando o ciclo. O semáforo tem ainda vários botões para controlo de situações especiais. Assim, o botão de pânico que faz com que o controlador mude o semáforo para a cor vermelha, qualquer que seja a anterior (método panic). O botão off faz com que o semáforo mude imediatamente para amarelo intermitente (método off). Esta situação é interrompida através dos botões on (volta ao comportamento normal; método on) ou de pânico. O botão on é a única forma de fazer semáforo sair da situação de pânico e recuperar o funcionamento normal. Além do código de controlo, o semáforo tem ainda um método (status) que permite saber a cor actual do semáforo. O semáforo começa intermitente e quando recupera das situações de intermitência e de pânico fica vermelho.

Implemente o semáforo e a sua máquina de estados.

Solução

A solução apresentada considera que a superclasse dos estados é interna (tem, desse modo, acesso ao estado interno do semáforo e não é necessário exportar nenhuma interface para gestão de estados.

Notar os aspectos de herança de classes internas e a forma especial de chamada ao contrutor da superclasse interna. Notar ainda como uma classe interna (State) pode aceder ao objecto actual da classe externa (TrafficLight.this).

Notar ainda que não é estritamente necessário que a superclasse das classes dos estados seja interna à classe detentora do estado (TrafficLight). Optou-se por definir a classe como interna para demonstrar como a classe interna pode aceder a estado privado da folha.

Ficheiro TrafficLight.java (classe TrafficLight (externa) e TrafficLight.State (interna))
public class TrafficLight {

    /** The traffic light's state. */
    private State _state;

    /**
     * Abstract state class.
     *
     * This class is internal so that it has access to the traffic light's
     * internal state. Actual states are subclasses which must use this class'
     * protected interface.
     */
    public abstract class State {

        /** Tick behavior. */
        public abstract void tick();

        /** Panic behavior. */
        public abstract void panic();

        /** "On" behavior. */
        public void on() { /* ignore by default */ }

        /** "Off" behavior. */
        public abstract void off();

        /** @return traffic light status. */
        public abstract String status();

        /**
         * Define the traffic light's new state.
         *
         * @param newState
         *            the new state.
         */
        protected void setState(State newState) {
            _state = newState;
        }

        /**
         * This method is needed so that new states can be created.
         *
         * @return the traffic light.
         */
        protected TrafficLight getLight() {
            return TrafficLight.this;
        }
    }

    /** Initialize traffic light. Starts blinking. */
    public TrafficLight() {
        _state = new Blinking(this);
    }

    /** Process on button press. */
    public void on() {
        System.out.print("[" + status() + "]");
        _state.on();
        System.out.println(" --(on)-> [" + status() + "]");
    }

    /** Process off button press. */
    public void off() {
        System.out.print("[" + status() + "]");
        _state.off();
        System.out.println(" --(off)-> [" + status() + "]");
    }

    /** Process panic button press. */
    public void panic() {
        System.out.print("[" + status() + "] --(panic)-> ");
        _state.panic();
        System.out.println("[" + status() + "]");
    }

    /** Process tick: switch light color. */
    public void tick() {
        System.out.print("[" + status() + "] --(tick)-> ");
        _state.tick();
        System.out.println("[" + status() + "]");
    }

    /** @return traffic light status. */
    public String status() {
        return _state.status();
    }
}


Abstract class for representing normal behavior. See also the next section (color classes).

Ticking states are those corresponding to the usual three colors.

Ficheiro Ticking.java
public abstract class Ticking extends TrafficLight.State {

    /**
     * @param light
     */
    Ticking(TrafficLight light) {
        light.super();
    }

    /**
     * All colors switch to blinking when "off" is pressed.
     *
     * @see TrafficLight.State#off()
     */
    @Override
    public void off() {
        setState(new Blinking(getLight()));
    }

    /**
     * All colors switch to panic when the button is pressed.
     *
     * @see TrafficLight.State#panic()
     */
    @Override
    public void panic() {
        setState(new Panic(getLight()));
    }
}

Red light.

Ficheiro Red.java
public class Red extends Ticking {

    /**
     * @param light
     */
    Red(TrafficLight light) {
        super(light);
    }

    /**
     * @see TrafficLight.State#tick()
     */
    @Override
    public void tick() {
        setState(new Green(getLight()));
    }

    /**
     * @see TrafficLight.State#status()
     */
    @Override
    public String status() {
        return "Red";
    }
}

Yellow light.

Ficheiro Yellow.java
public class Yellow extends Ticking {

    /**
     * @param light
     */
    Yellow(TrafficLight light) {
        super(light);
    }

    /**
     * @see TrafficLight.State#tick()
     */
    @Override
    public void tick() {
        setState(new Red(getLight()));
    }

    /**
     * @see TrafficLight.State#status()
     */
    @Override
    public String status() {
        return "Yellow";
    }
}

Green light.

Ficheiro Green.java
public class Green extends Ticking {

    /**
     * @param light
     */
    Green(TrafficLight light) {
        super(light);
    }

    /**
     * @see TrafficLight.State#tick()
     */
    @Override
    public void tick() {
        setState(new Yellow(getLight()));
    }

    /**
     * @see TrafficLight.State#status()
     */
    @Override
    public String status() {
        return "Green";
    }
}

Panic state.

Ficheiro Panic.java
public class Panic extends TrafficLight.State {

    /**
     * @param light
     */
    Panic(TrafficLight light) {
        light.super();
    }

    /**
     * Return to "ticking" (normal) mode.
     *
     * @see TrafficLight.State#on()
     */
    @Override
    public void on() {
        setState(new Red(getLight()));
    }

    /**
     * @see TrafficLight.State#off()
     */
    @Override
    public void off() {
        // do nothing: already off
    }

    /**
     * @see TrafficLight.State#panic()
     */
    @Override
    public void panic() {
        // do nothing: already in panic mode
    }

    /**
     * @see TrafficLight.State#tick()
     */
    @Override
    public void tick() {
        // do nothing: ignore ticks in panic mode
    }

    /**
     * @see TrafficLight.State#status()
     */
    @Override
    public String status() {
        return "Panic";
    }
}

Blinking light (off).

Ficheiro Blinking.java
public class Blinking extends TrafficLight.State {

    /**
     * @param light
     */
    Blinking(TrafficLight light) {
        light.super();
    }

    /**
     * Return to "ticking" (normal) mode.
     *
     * @see TrafficLight.State#on()
     */
    @Override
    public void on() {
        setState(new Red(getLight()));
    }

    /**
     * @see TrafficLight.State#off()
     */
    @Override
    public void off() {
        // do nothing: already off
    }

    /**
     * @see TrafficLight.State#panic()
     */
    @Override
    public void panic() {
        setState(new Panic(getLight()));
    }

    /**
     * @see TrafficLight.State#tick()
     */
    @Override
    public void tick() {
        // do nothing: ignore ticks
    }

    /**
     * @see TrafficLight.State#status()
     */
    @Override
    public String status() {
        return "Blinking";
    }
}

Simple application to illustrate the traffic light's state changes.

Ficheiro App.java
public class App {
    /**
     * @param args
     */
    public static void main(String[] args) {
        TrafficLight light = new TrafficLight();
        System.out.println("Light status: " + light.status());
        light.off();
        light.panic();
        light.on();
        light.tick();
        light.tick();
        light.tick();
        light.tick();
        light.panic();
        light.off();
        light.on();
        light.off();
    }
}

Compiling and Running

Compiling

Running

java App

Result:

Light status: Blinking
[Blinking] --(off)-> [Blinking]
[Blinking] --(panic)-> [Panic]
[Panic] --(on)-> [Red]
[Red] --(tick)-> [Green]
[Green] --(tick)-> [Yellow]
[Yellow] --(tick)-> [Red]
[Red] --(tick)-> [Green]
[Green] --(panic)-> [Panic]
[Panic] --(off)-> [Panic]
[Panic] --(on)-> [Red]
[Red] --(off)-> [Blinking]