Chapter 19 - Exercise 11

(7) In a real vending machine you will want to easily add and change the type of vended items, so the limits imposed by an enum on Input are impractical (remember that enums are for a restricted set of types). Modify VendingMachine.java so that the vended items are represented by a class instead of being part of Input, and initialize an Array List of these objects from a text file (using net.mindview.util.TextFile).


思路也很简单,把原先的商品设计成Product类,允许从外部文件读入商品条目。售货机初始化时完成载入商品条目。再把原先ITEM_SELECTION枚举型Input改成一个标记。当状态机收到ITEM_SELECTION型状态时,next()方法变为从商品条目列表中随机读取一个商品。

Input11.java



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

public enum Input11 {

    /**
     *  枚举成员
     */
    NICKEL(5), DIME(10), QUARTER(25), DOLLAR(100), PRODUCT(0),
    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
    Input11(int value) { this.value = value; }
    Input11() {}
    int amount() { return value; }; // In cents
    static Random rand = new Random();
    public static Input11 randomSelection() {
        // Don’t include STOP:
        return values()[rand.nextInt(values().length - 1)];
    }
}


RandomInputGenerator11.java



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

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


Category11.java



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

public enum Category11 {
    MONEY(NICKEL, DIME, QUARTER, DOLLAR),
    ITEM_SELECTION(PRODUCT),
    QUIT_TRANSACTION(ABORT_TRANSACTION),
    SHUT_DOWN(STOP);

    private static EnumMap<Input11,Category11> categories = new EnumMap<Input11,Category11>(Input11.class);

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

    public static Category11 categorize(Input11 input) {
        return categories.get(input);
    }

    private Input11[] values;
    Category11(Input11... types) { values = types; }

    public static void main(String[] args){
        System.out.println(Category11.MONEY);
        System.out.println(Category11.categories);
    }
}


VendingMachine11.java



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

public class VendingMachine11 {
    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 Product selection = null;
    private EnumMap<State,StateMachine> em=new EnumMap<State,StateMachine>(State.class);
    
    private class StateMachine {
        void next(Input11 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 VendingMachine11(String path) {
        Product.loadProducts(path);
        em.put(State.RESTING,new StateMachine(){
            void next(Input11 input) {
                switch(Category11.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(Input11 input) {
                switch(Category11.categorize(input)) {
                    case MONEY:
                        amount += input.amount();
                        break;
                    case ITEM_SELECTION:
                        selection = Product.randomSelection();
                        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<Input11> 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) {
        VendingMachine11 vm=new VendingMachine11("/Users/Wei/java/com/ciaoshen/thinkinjava/chapter19/product.txt");
        vm.run(new RandomInputGenerator11());
    }
}


TextFile.java



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

public class TextFile extends ArrayList<String> {
    // Read a file as a single string:
    public static String read(String fileName) {
        StringBuilder sb = new StringBuilder();
        try {
            BufferedReader in= new BufferedReader(new FileReader(new File(fileName).getAbsoluteFile()));
            try {
                String s;
                while((s = in.readLine()) != null) {
                    sb.append(s);
                    sb.append("\n");
                }
            } finally {
                in.close();
            }
        } catch(IOException e) {
            throw new RuntimeException(e);
        }
        return sb.toString();
    }

    public TextFile(String fileName, String splitter) {
        super(Arrays.asList(read(fileName).split(splitter)));
        // Regular expression split() often leaves an empty
        // String at the first position:
        if(get(0).equals("")) remove(0);
    }
    // Normally read by lines:
    public TextFile(String fileName) {
        this(fileName, "\n");
    }
}


product.txt

存放商品条目的文本文件。每行存放一种商品的信息。第一项:商品名,第二项:价格。中间用tab符号分隔。



TOOTTHPASTE	200
CHIPS	75
SODA	100
SOAP	50


Exercise11.java



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

public class Exercise11 {
    public static void main(String[] args) {
        VendingMachine11 vm=new VendingMachine11("/Users/Wei/java/com/ciaoshen/thinkinjava/chapter19/product.txt");
        Generator<Input11> gen=new RandomInputGenerator11();
        vm.run(gen);
    }
}



Need Java Developers? Hire Me »

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

I live in CANADA now.