Java进阶 1-2 枚举

目录

常量特定方法

职责链模式的枚举实现

状态机模式的枚举实现

多路分发

1、使用枚举类型实现分发

2、使用常量特定方法实现分发

3、使用EnumMap实现分发

4、使用二维数组实现分发


本笔记参考自: 《On Java 中文版》


常量特定方法

        在Java中,我们可以通过为每个枚举实例编写不同的方法,来赋予它们不同的行为。一个方式是在枚举类型中定义抽象方法,并在枚举实例中实现它:

【例子:为枚举实例编写方法】

import java.text.DateFormat;
import java.util.Date;

public enum ConstantSpecificMethod {
    DATE_TIME {
        @Override
        String getInfo() {
            return DateFormat.getDateInstance()
                    .format(new Date());
        }
    },
    CLASSPATH {
        @Override
        String getInfo() {// getenv()方法用于获取指定环境变量的值
            return System.getenv("CLASSPATH");
        }
    },
    VERSION {
        @Override
        String getInfo() { // 获取由输入指定的属性
            return System.getProperty("java.version");
        }
    };

    abstract String getInfo();

    public static void main(String[] args) {
        for (ConstantSpecificMethod csm : values())
            System.out.println(csm.getInfo());
    }
}

        程序执行的结果是:

        在这个例子中,我们通过关联的枚举实例来调用对应的方法,这通常被称为表驱动模式

        这里需要复习一点:在面向对象的编程中,不同的行为是和不同的类相关联的。而上述枚举的每个实例都拥有各自不同的行为,这就表明每个实例都相当于不同的类型(体现了多态)。它们的共同点在于它们的基类ConstantSpecificMethod

        但这并不意味着枚举实例能够等价于普通的类:

【例子:枚举实例和类之间的不同点】

enum LikeClasses {
    A {
        @Override
        void behavior() {
            System.out.println("A");
        }
    },
    B {
        @Override
        void behavior() {
            System.out.println("B");
        }
    },
    C {
        @Override
        void behavior() {
            System.out.println("C");
        }
    };

    abstract void behavior();
}

public class NotClasses {
//    void f1(LikeClasses.A instance) {
//    } // 不可行的操作
}

        编译器并不允许f1()的操作:将枚举实例作为类的类型进行使用。为了解释这一点,可以对程序进行反编译javap -c LikeClasses

反编译告诉我们,每个枚举元素都是LikeClasses的一个static final实例。实例无法作为类型进行使用。

        除此之外,不同于内部类,因为枚举实例是静态的,所以我们无法通过外部类LikeClasses直接访问其内部的枚举实例(具体而言,我们无法在非静态域中访问外部类的静态成员)。

(与匿名内部类相比,常量特定方法要显得更加简洁。)

        除此之外,常量特定方法也支持重写

【例子:重写常量特定方法】

public enum OverrideConstantSpecific {
    NUT, BOLT,
    WASHER {
        @Override
        public void f() {
            System.out.println("重写后的f()方法");
        }
    };

    void f() {
        System.out.println("默认的f()方法");
    }

    public static void main(String[] args) {
        for (OverrideConstantSpecific ocs : values()) {
            System.out.print(ocs + ": ");
            ocs.f();
        }
    }
}

        程序执行的结果是:

    这些特性使得我们在通常情况下,可以将枚举作为类来使用。

职责链模式的枚举实现

||| 职责链模式:先创建一批用于解决目标问题的不同方法,并将它们链式排列。当一个请求到达时,它会顺着这条“链”向下走,直到遇到可以解决当前请求的方法。

        下面的例子描述了一个邮局模型:使用常规方法处理信件,当前方法不可行时,尝试其他方法,直到无法处理该信件为止(称为“死信”)。

    可以将每种处理方法视作一种策略,策略的列表组成了一条职责链。

【例子:邮局模型】

import onjava.Enums;

import java.util.Iterator;

class Mail {
    enum GeneralDelivery {YES, NO1, NO2, NO3, NO4, NO5} // 是否使用常规方式处理

    enum Scannability {UNSCANNABLE, YES1, YES2, YES3, YES4} // 是否可以扫描

    enum Readability {ILLEGIBLE, YES1, YES2, YES3, YES4} // 是否可读

    enum Address {INCORRECT, OK1, OK2, OK3, OK4, OK5, OK6}

    enum ReturnAddress {MISSING, OK1, OK2, OK3, OK4, OK5}

    // 创建枚举变量
    GeneralDelivery generalDelivery;
    Scannability scannability;
    Readability readability;
    Address address;
    ReturnAddress returnAddress;
    static long counter = 0;
    long id = counter++;

    @Override
    public String toString() {
        return "邮件" + id;
    }

    public String details() {
        return toString() +
                ",一般交付:" + generalDelivery + ",地址是否可以扫描:" + scannability +
                "\n地址是否可读:" + readability + ",目标地址:" + address +
                "\n返还地址:" + returnAddress;
    }

    // 用于生成测试邮件:
    // (Enums类可见笔记 进阶1-1)
    public static Mail randomMail() {
        Mail m = new Mail();

        // 为枚举实例随机赋值:
        m.generalDelivery =
                Enums.random(GeneralDelivery.class);
        m.scannability =
                Enums.random(Scannability.class);
        m.readability =
                Enums.random(Readability.class);
        m.address =
                Enums.random(Address.class);
        m.returnAddress =
                Enums.random(ReturnAddress.class);

        return m;
    }

    // count无需修改,可以使用final
    public static Iterable<Mail> generator(final int count) {
        return new Iterable<Mail>() {
            int n = count;

            @Override
            public Iterator<Mail> iterator() {
                return new Iterator<Mail>() {
                    @Override
                    public boolean hasNext() {
                        return n-- > 0;
                    }

                    @Override
                    public Mail next() {
                        return randomMail();
                    }
                };
            }
        };
    }
}

public class PostOffice {
    // 使用职责链模式:
    enum MailHandler {
        GENERAL_DELIVERY {
            @Override
            boolean handle(Mail m) {
                switch (m.generalDelivery) {
                    case YES:
                        System.out.println(
                                "对" + m + "使用常规方法进行处理");
                        return true;
                    default:
                        return false;
                }
            }
        },
        MACHINE_SCAN {
            @Override
            boolean handle(Mail m) {
                switch (m.scannability) {
                    case UNSCANNABLE:
                        return false;
                    default:
                        switch (m.address) {
                            case INCORRECT:
                                return false;
                            default:
                                System.out.println("自动派送" + m);
                                return true;
                        }
                }
            }
        },
        VISUAL_INSPECTION {
            @Override
            boolean handle(Mail m) {
                switch (m.readability) {
                    case ILLEGIBLE:
                        return false;
                    default:
                        switch (m.address) {
                            case INCORRECT:
                                return false;
                            default:
                                System.out.println("常规派送" + m);
                                return true;
                        }
                }
            }
        },
        RETURN_TO_SENDER {
            @Override
            boolean handle(Mail m) {
                switch (m.returnAddress) {
                    case MISSING:
                        return false;
                    default:
                        System.out.println(
                                "将" + m + "返还给发送者");
                        return true;
                }
            }
        };

        abstract boolean handle(Mail m);
    }

    static void handle(Mail m) {
        for (MailHandler handler : MailHandler.values())
            if (handler.handle(m))
                return;
        System.out.println(m + "是一封死信");
    }

    public static void main(String[] args) {
        for (Mail mail : Mail.generator(5)) {
            System.out.println(mail.details());
            handle(mail);
            System.out.println("============");
        }
    }
}

        程序执行的结果是:

        职责链模式MailHandler枚举中得到应用,并且枚举的定义顺序决定了各个策略在邮件上的应用顺序。


状态机模式的枚举实现

||| 状态机的解释:

  1. 具有有限数量的特定状态。
  2. 每个状态都有输入。根据输入,可以实现状态之间的跳转,但也存在瞬态
  3. 当任务执行完毕后,立即跳出所有状态。

        用枚举表示状态机模式的一个优势是,枚举可以限制出现的状态集的大小:

【例子:自动售货机】

        首先创建一个用于输入的枚举:

import java.util.Random;

public enum Input {
    CENT(1), COIN(10), TEN_COIN(100),
    TOOTHBRUSH(150.0f), TOWEL(200.0f), SOAP(80.0f),
    ABORT_TRANSACTION {
        @Override
        public float amount() {
            throw new RuntimeException("ABORT.amount()");
        }
    },
    STOP { // STOP必须是最后一个实例

        @Override
        public float amount() {
            throw new RuntimeException("SHUT_DOWN.amount()");
        }
    };

    float value;

    Input(float value) {
        this.value = value;
    }

    Input() {
    }

    float amount() { // 单位为:毛
        return value;
    }

    static Random rand = new Random(47);

    public static Input randomSelection() {
        // 不包括STOP:
        return values()[rand.nextInt(values().length - 1)];
    }
}

        amount()会返回物品对应的价格。由于枚举的限制,amount()会作用于所有的枚举实例。很显然,对最后两个实例调用它并不合适,因此这里需要重写amount()

        接下来实现自动售货机的状态机:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.function.Supplier;
import java.util.stream.Collectors;

enum Category {

    MONEY(Input.CENT, Input.COIN, Input.TEN_COIN),
    ITEM_SELECTION(Input.TOOTHBRUSH, Input.TOWEL, Input.SOAP),
    QUIT_TRANSACTION(Input.ABORT_TRANSACTION),
    SHUT_DOWN(Input.STOP);
    private Input[] values;

    Category(Input... types) {
        values = types;
    }

    private static EnumMap<Input, Category> categories =
            new EnumMap<>(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);
    }
}

public class VendingMachine {
    private static State state = State.RESTING;
    private static float amount = 0;
    private static Input selection = null;

    enum StateDuration { // 用于标记enum中的瞬态
        TRANSIENT;
    }

    enum State {
        RESTING { // 休眠中
            @Override
            void next(Input input) {
                switch (Category.categorize(input)) {
                    case MONEY:
                        amount += input.amount();
                        state = ADDING_MONEY; // 重新赋值state
                        break;
                    case SHUT_DOWN:
                        state = TERMINAL;
                    default:
                }
            }
        },
        ADDING_MONEY { // 添加金钱,购买商品
            @Override
            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("当前余额不足以购买" + selection);
                        else
                            state = DISPENSING; // 下个状态也可以是瞬态
                        break;
                    case QUIT_TRANSACTION:
                        state = GIVING_CHANGE;
                        break;
                    case SHUT_DOWN:
                        state = TERMINAL;
                    default:
                }
            }
        },
        DISPENSING(StateDuration.TRANSIENT) { // 取出商品(注意:实例的初始化方式并不相同)
            @Override
            void next() {
                System.out.println("这是你购买的物品:" + selection);
                amount -= selection.amount();
                state = GIVING_CHANGE;
            }
        },
        GIVING_CHANGE(StateDuration.TRANSIENT) { // 找钱
            @Override
            void next() {
                if (amount > 0) {
                    System.out.println("这是找零:" + amount);
                    amount = 0;
                }
                state = RESTING;
            }
        },
        TERMINAL { // 关闭机器
            @Override
            void output() {
                System.out.println("停止运行");
            }
        };

        // 判断当前状态是否为瞬态
        private boolean isTransient = false;

        State() {
        }

        State(StateDuration trans) {
            isTransient = true; // 说明当前处于瞬态
        }

        void next(Input input) {
            throw new RuntimeException("仅应该对非瞬态调用next(Input input)");
        }

        void next() {
            throw new RuntimeException("仅应该对StateDuration.TRANSIENT(瞬态)调用next()");
        }

        void output() {
            System.out.println(amount);
        }
    }

    static void run(Supplier<Input> gen) {
        while (state != State.TERMINAL) {
            state.next(gen.get());
            while (state.isTransient)
                state.next();
            state.output();
        }
    }

    public static void main(String[] args) {
        Supplier<Input> gen = new RandomInputSupplier();
        if (args.length == 1)
            gen = new FileInputSupplier(args[0]);
        run(gen);
    }
}

class RandomInputSupplier implements Supplier<Input> {
    @Override
    public Input get() {
        return Input.randomSelection();
    }
}

// 从文件输入:
class FileInputSupplier implements Supplier<Input> {
    private Iterator<String> input;

    FileInputSupplier(String fileName) {
        try {
            input = Files.lines(Paths.get(fileName)) // 行级的Stream流
                    .flatMap(s -> Arrays.stream(s.split(";"))) // 输入文件的信息是由分号分隔的
                    .map(String::trim)
                    .collect(Collectors.toList())
                    .iterator();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Input get() {
        if (!input.hasNext())
            return null;
        return Enum.valueOf(Input.class, input.next().trim());
    }
}

        若不从文件导入数据,则售货机在开机后就会一直运行下去。为了完整演示整个状态机的流程,这里使用文件输入。测试文件如下:

CENT; TEN_COIN; TOOTHBRUSH;
COIN; TEN_COIN; TEN_COIN; SOAP;
TEN_COIN;  TEN_COIN; CENT; COIN; TOWEL;
ABORT_TRANSACTION;
STOP;

        对应该文件,程序执行的结果是:

        在这种模式中,枚举在switch上发挥了它的功能。

        在VendingMachine类中存在着两个瞬态:DISPENSINGGIVING_CHANGE。状态机会在各种状态之间移动,知道不再处于某个瞬态中为止(对这个例子而言,“取出商品”和“找零”的动作是瞬态,售货机不应该停留在这些状态中)。

    然而,这种设计存在一个限制:State的枚举实例是静态的,这意味着这些实例只能访问其外部类(即VendingMachine)中的静态字段。这意味着只能存在一个VendingMachine实例。

多路分发

        若一个程序中存在多个交互类型,这个程序有可能变得很混乱。举一个例子:

public abstract class Number {
    abstract Number plus(Number number);
    abstract Number multiply(Number number);
    //...
}

此处的Number类是某个系统中所有数值类型的基类,其中包括一些进行数值运算所需要的方法。

        现在假设ab分别属于Number的某个子类,并且都使用Number引用指示它们。若要处理语句a.plus(b),一个简单的想法是我们需要同时知道ab的具体类型。遗憾的是,Java只能进行单路分发,这意味着我们只能对一个类型未知的对象进行操作

        多路分发并不是同时对多个类型未知的对象进行操作,而是进行了多次的方法调用

    使用多路分发,就必须对每个类型都执行虚拟调用。并且,若操作发生在不同的交互类型层次结构中,则还需要对每个层次结构都执行虚拟操作。

        接下来通过一个“猜拳”的例子展示多路分发的使用:

【例子:“猜拳”游戏】

        首先确定“猜拳”的三种结果:

public enum Outcome {
    WIN, // 赢
    LOSE, // 输
    DRAW // 平局
}

        接下来实现多路分发的接口:

public interface Item {
    Outcome compete(Item it);

    Outcome eval(Paper p);

    Outcome eval(Scissors p);

    Outcome eval(Rock p);
}

        接下来为“石头”、“剪刀”和“布”分别匹配它们的胜负情况:

        然后,通过调用compete()方法,就可以简单地判断“猜拳”的胜负:

import java.util.Random;

public class RoShamBo1 {
    static final int SIZE = 10;
    private static Random rand = new Random(47);

    public static Item newItem() {
        switch (rand.nextInt(3)) {
            default: // 必须有的default
            case 0:
                return new Scissors();
            case 1:
                return new Paper();
            case 2:
                return new Rock();
        }
    }

    public static void match(Item a, Item b) {
        System.out.println(a + " vs. " + b + " " + a.compete(b));
    }

    public static void main(String[] args) {
        for (int i = 0; i < SIZE; i++)
            match(newItem(), newItem());
    }
}

        程序执行的结果是:

        在match()方法中,程序可以知道a的类型,进而调用对应的compete()方法。在compete()中调用eval()方法时,会向其中传入一个this引用,这个this引用用于保留原本a的类型。

    如上所示,多路分发的构筑较为麻烦。但好处显而易见:我们在调用中保持住了语法的优雅。

        因为枚举实例不是类型,因此我们无法通过重载eval()方法的方式实现目标。但还是有别的方法可以进行实现。 

1、使用枚举类型实现分发

        一种方法是在初始化枚举实例时进行操作。通过这种方式,我们最终可以得到一个类似于查询表的结构:

【例子:初始化枚举实现“猜拳”】

import static enums.Outcome.*;

// 规定Competitor接口,通过接口来操作枚举:
public enum RoShamBo2 implements Competitor<RoShamBo2> {
    PAPER(DRAW, LOSE, WIN),
    SCISSORS(WIN, DRAW, LOSE),
    ROCK(LOSE, WIN, DRAW);
    private Outcome vPAPER, vSCISSORS, vROCK;

    // 针对“石头”、“剪刀”和“布”这三种情况,初始化后三个私有变量的值也会发生改变
    RoShamBo2(Outcome paper,
              Outcome scissors, Outcome rock) {
        this.vPAPER = paper;
        this.vSCISSORS = scissors;
        this.vROCK = rock;
    }

    @Override
    public Outcome compete(RoShamBo2 competitor) {
        switch (competitor) {
            default:
            case ROCK: // 若对方出的是石头
                return vROCK; // 那么就返回当前对象遇到石头时会得出的结果
            case PAPER: // 布
                return vPAPER;
            case SCISSORS: // 剪刀
                return vSCISSORS;
        }
    }

    public static void main(String[] args) {
        RoShamBo.play(RoShamBo2.class, 10); // 独立出来的方法,用于操作猜拳
    }
}

        程序执行的结果是:

        书本在此处将原本用于操作枚举的代码分隔开来,以此增加了代码的复用率。以下是复用的代码(Competitor接口及RoShamBo类)的实现:

【例子:用于操作枚举的工具】

        为了统一现有的枚举及之后会出现的枚举的操作,可以使用Competitor接口:

public interface Competitor<T extends Competitor<T>> {
    Outcome compete(T competitor);
}

        我们规定,所有的“石头剪刀布”枚举都来自于这种竞争者(Competitor)关系,自限定类型笔记18-4)可以很好地满足这一点。

    与一般的泛型相比,自限定类型对继承关系有更加严格的要求。对于上述的compete()方法而言,它只会接受Competitor及其子类。

         然后是生成随机结果的RoShamBo类:

import onjava.Enums;

public class RoShamBo extends EnumClass {
    // 使用了自限定类型:
    public static <T extends Competitor<T>>
    void match(T a, T b) {
        System.out.println(a + " vs. " + b + " " + a.compete(b));
    }

    // 这种方式同时继承了一个基类和一个接口:
    public static <T extends Enum<T> & Competitor<T>>
    void play(Class<T> rsbClass, int size) {
        for (int i = 0; i < size; i++)
            match(Enums.random(rsbClass), Enums.random(rsbClass));
    }
}

        这个类包含了一个用于接收的paly()方法和真正调用枚举操作的match()方法。play()方法规定,传入的数据必须是一个实现了Competitor接口的枚举。

        需要注意一点:由于play()方法不需要返回值,换言之,在离开泛型的边界上并没有需要使用到类型参数T的地方,因此参数T在这里似乎是不必要的。但如果将Class<T>替换为Class<?>这种通配符形式,就会发生报错:

这是因为通配符【?】无法继承多个基类,因此这里只能使用类型参数T


2、使用常量特定方法实现分发

        遗憾的是,枚举本身并不能作为对象类型使用。因此我们没有办法通过传递实例来进行多路分发。在这里,一个好的办法是使用switch语句:

【例子:使用switch分发】

import static enums.Outcome.*;

public enum RoShamBo3 implements Competitor<RoShamBo3> {
    PAPER {
        @Override
        public Outcome compete(RoShamBo3 competitor) {
            switch (competitor) {
                default:
                case PAPER:
                    return DRAW;
                case SCISSORS:
                    return LOSE;
                case ROCK:
                    return WIN;
            }
        }
    },
    SCISSORS {
        @Override
        public Outcome compete(RoShamBo3 competitor) {
            switch (competitor) {
                default:
                case PAPER:
                    return WIN;
                case SCISSORS:
                    return DRAW;
                case ROCK:
                    return LOSE;
            }
        }
    },
    ROCK {
        @Override
        public Outcome compete(RoShamBo3 competitor) {
            switch (competitor) {
                default:
                case PAPER:
                    return LOSE;
                case SCISSORS:
                    return WIN;
                case ROCK:
                    return DRAW;
            }
        }
    };

    // 此处的方法声明只是为了便于理解:
    @Override
    public abstract Outcome compete(RoShamBo3 competitor);

    public static void main(String[] args) {
        RoShamBo.play(RoShamBo3.class, 10);
    }
}

        程序执行的结果与上一个例子相同:

        这种写法无疑是繁琐的。因此,我们可以尝试对其进行优化。用三目运算符来取代switch语句,压缩代码空间:

【例子:使用三目运算符进行优化】

public enum RoShamBo4 implements Competitor<RoShamBo4> {
    ROCK {
        @Override
        public Outcome compete(RoShamBo4 competitor) {
            return compete(SCISSORS, competitor);
        }
    },
    SCISSORS {
        @Override
        public Outcome compete(RoShamBo4 competitor) {
            return compete(PAPER, competitor);
        }
    },
    PAPER {
        @Override
        public Outcome compete(RoShamBo4 competitor) {
            return compete(ROCK, competitor);
        }
    };

    // 重载的compete()方法
    // 第一个参数表示的是胜利条件
    Outcome compete(RoShamBo4 loser, RoShamBo4 competitor) {
        return ((competitor == this) ? Outcome.DRAW : // 通过this确定平局条件
                (competitor == loser) ? Outcome.WIN : // 通过传入参数确定胜利条件
                        Outcome.LOSE);  // 那么剩下的就是失败条件了
    }

    public static void main(String[] args) {
        RoShamBo.play(RoShamBo4.class, 10);
    }
}

        由于输出结果是一致的,因此此处不再展示。

    当然,更加简短的代码有可能带来更大的理解成本。在大型的系统开发过程中应该要注意这一点。


3、使用EnumMap实现分发

        EnumMap是专门为enum设计的高效Map。使用它,我们可以更好地实现多路分发。具体而言,当我们需要进行两路分发时,我们可以使用嵌套的EnumMap

【例子:使用EnumMap实现分发】

import java.util.EnumMap;

import static enums.Outcome.*;

public enum RoShamBo5 implements Competitor<RoShamBo5> {
    PAPER, SCISSORS, ROCK;

    // 使用Class进行初始化,规定Map的键值必须是RoShamBo5类型
    static EnumMap<RoShamBo5, EnumMap<RoShamBo5, Outcome>>
            table = new EnumMap<>(RoShamBo5.class);

    static {
        // 使用for循环,为table每个键创建其对应的对象
        for (RoShamBo5 it : RoShamBo5.values())
            table.put(it, new EnumMap<>(RoShamBo5.class));
        // 为对象填充具体的信息
        initRow(PAPER, DRAW, LOSE, WIN);
        initRow(SCISSORS, WIN, DRAW, LOSE);
        initRow(ROCK, LOSE, WIN, DRAW);
    }

    static void initRow(RoShamBo5 it,
                        Outcome vPAPER, Outcome vSCISSORS, Outcome vROCK) {
        EnumMap<RoShamBo5, Outcome> row =
                RoShamBo5.table.get(it); // get()方法会返回指定键对应的值(一个EnumMap的对象)
        row.put(RoShamBo5.PAPER, vPAPER);
        row.put(RoShamBo5.SCISSORS, vSCISSORS);
        row.put(RoShamBo5.ROCK, vROCK);
    }

    @Override
    public Outcome compete(RoShamBo5 competitor) {
        return table.get(this).get(competitor); // 两次get(),两次分发
    }

    public static void main(String[] args) {
        RoShamBo.play(RoShamBo5.class, 10);
    }
}

        这里需要注意的是static子句。我们需要在RoShamBo5的其余部分初始化完毕之前,将所有的胜负信息填充到table里面。因此需要使用静态的初始化方式。


4、使用二维数组实现分发

        在“石头剪刀布”中,每一个枚举实例都会对应一个固定的值(可以用ordinal()获取)。这使得使用二维数组实现两路分发成为可能:

【例子:使用二维数组实现分发】

import static enums.Outcome.*;

public enum RoShamBo6 implements Competitor<RoShamBo6> {
    PAPER, SCISSORS, ROCK;

    // 胜负顺序需要根据枚举的定义顺序进行调整:
    private static Outcome[][] table = {
            {DRAW, LOSE, WIN}, // 布
            {WIN, DRAW, LOSE}, // 剪刀
            {LOSE, WIN, DRAW}, // 石头
    };

    @Override
    public Outcome compete(RoShamBo6 competitor) {
        return table[this.ordinal()][this.ordinal()];
    }

    public static void main(String[] args) {
        RoShamBo.play(RoShamBo6.class, 10);
    }
}

        这种写法有着极高的效率,以及简短易懂的代码。但与之相对,它的缺点也正是在于其使用了数组:一方面,在面对更加大量的数据时,使用数组或许会漏掉某些麻烦的情况;除此之外,这种写法更为死板——从数组中得到的依旧只是一个常量的结果。

    针对第二个缺点,我们可以使用函数对象来进行一些变化,使得数组不再那么僵硬。并且,在处理特定问题时,“表驱动模式”可以发挥出强大的力量。

  • 21
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值