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.

[Expand] 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.

[Expand] 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.

[Expand] 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.

[Expand] 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.

[Expand] 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.

[Expand] 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).

[Expand] 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.

[Expand] 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]