Chapter 19 - Exercise 10

(7) Modify class VendingMachine (only) using EnumMap so that one program can have multiple instances of VendingMachine.


思路很简单,利用“命令模式”,把状态机的“状态”和“操作”分离。“状态”继续保持静态,放在State枚举型里。但具体的next()操作作为value和“状态”一起存入EnumMap里。而EnumMap作为VendingMachine的一个成员字段被维护。

Input.java



package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public enum Input {

    /**
     *  枚举成员
     */
    NICKEL(5), DIME(10), QUARTER(25), DOLLAR(100), TOOTHPASTE(200), CHIPS(75), SODA(100), SOAP(50),
    ABORT_TRANSACTION {
        public int amount() { // Disallow
            throw new RuntimeException("ABORT.amount()");
        }
    },
    STOP { // This must be the last instance.
        public int amount() { // Disallow
            throw new RuntimeException("SHUT_DOWN.amount()");
        }
    };

    /**
     *  其他成员
     */
    int value; // In cents
    Input(int value) { this.value = value; }
    Input() {}
    int amount() { return value; }; // In cents
    static Random rand = new Random();
    public static Input randomSelection() {
        // Don’t include STOP:
        return values()[rand.nextInt(values().length - 1)];
    }
}


Category.java



package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;
import static com.ciaoshen.thinkinjava.chapter19.Input.*;

public enum Category {
    MONEY(NICKEL, DIME, QUARTER, DOLLAR),
    ITEM_SELECTION(TOOTHPASTE, CHIPS, SODA, SOAP),
    QUIT_TRANSACTION(ABORT_TRANSACTION),
    SHUT_DOWN(STOP);

    private Input[] values;
    Category(Input... types) { values = types; }
    private static EnumMap<Input,Category> categories = new EnumMap<Input,Category>(Input.class);

    static {
        for(Category c : Category.class.getEnumConstants()) {
            for(Input type : c.values){
                categories.put(type, c);
            }
        }
    }

    public static Category categorize(Input input) {
        return categories.get(input);
    }
}


RandomInputGenerator.java



package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

// For a basic sanity check:
public class RandomInputGenerator implements Generator<Input> {
    public Input next() { return Input.randomSelection(); }
}


VendingMachine.java



package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public class VendingMachine {
    enum StateDuration { TRANSIENT } // Tagging enum
    enum State {
        RESTING, ADDING_MONEY, DISPENSING(StateDuration.TRANSIENT), GIVING_CHANGE(StateDuration.TRANSIENT),
        TERMINAL;
        
        private boolean isTransient = false;
        State() {}
        State(StateDuration trans) { isTransient = true; }
        
    }
    
    private State state = State.RESTING;
    private int amount = 0;
    private Input selection = null;
    private EnumMap<State,StateMachine> em=new EnumMap<State,StateMachine>(State.class);
    
    private class StateMachine {
        void next(Input input) {
            throw new RuntimeException("Only call " + "next(Input input) for non-transient states");
        }
        void next() {
            throw new RuntimeException("Only call next() for " + "StateDuration.TRANSIENT states");
        }
        void output() { System.out.println(amount); }
    }
    
    public VendingMachine() {
        em.put(State.RESTING,new StateMachine(){
            void next(Input input) {
                switch(Category.categorize(input)) {
                    case MONEY:
                        amount += input.amount();
                        state = State.ADDING_MONEY;
                        break;
                    case SHUT_DOWN:
                        state = State.TERMINAL;
                    default:
                }
            }
        });
        em.put(State.ADDING_MONEY,new StateMachine(){
            void next(Input input) {
                switch(Category.categorize(input)) {
                    case MONEY:
                        amount += input.amount();
                        break;
                    case ITEM_SELECTION:
                        selection = input;
                        if(amount < selection.amount())
                            System.out.println("Insufficient money for " + selection);
                        else state = State.DISPENSING;
                        break;
                    case QUIT_TRANSACTION:
                        state = State.GIVING_CHANGE;
                        break;
                    case SHUT_DOWN:
                        state = State.TERMINAL;
                    default:
                }
            }
        });
        em.put(State.DISPENSING,new StateMachine(){
            void next() {
                System.out.println("here is your " + selection);
                amount -= selection.amount();
                state = State.GIVING_CHANGE;
            }
        });
        em.put(State.GIVING_CHANGE,new StateMachine(){
            void next() {
                if(amount > 0) {
                    System.out.println("Your change: " + amount);
                    amount = 0;
                }
                state = State.TERMINAL;
            }
        });
        em.put(State.TERMINAL,new StateMachine(){
            void output() { System.out.println("Halted"); }
        });
    }
    
    public void reset(){
        state = State.RESTING;
        amount = 0;
        selection = null;
    }
               
    public void run(Generator<Input> gen) {
        while(state != State.TERMINAL) {
            em.get(state).next(gen.next());
            while(state.isTransient){
                em.get(state).next();
            }
            em.get(state).output();
        }
    }
    
    public static void main(String[] args) {
        VendingMachine vm=new VendingMachine();
        vm.run(new RandomInputGenerator());
    }
}


Exercise10.java



package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public class Exercise10 {
    public static void main(String[] args) {
        VendingMachine vm1=new VendingMachine();
        Generator<Input> gen=new RandomInputGenerator();
        vm1.run(gen);
        vm1.reset();
        vm1.run(gen);
        
        VendingMachine vm2=new VendingMachine();
        vm2.run(gen);
    }
}



Need Java Developers? Hire Me »

I know Java. Talk is cheap, watch my code.

I live in CANADA now.