Effective Java读书笔记十一(Java Tips.Day.11)

TIP34 用接口模拟可伸缩的枚举

原书表达的太绕口了,还是直接上代码吧,这样直观:

public interface Operation {
    double apply(double x, double y);
}
public enum BasicOperation implements Operation{

    PLUS("+"){
        @Override
        public double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS("-"){
        @Override
        public double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES("*"){
        @Override
        public double apply(double x, double y) {
            return x * y;
        }
    },
    DEVIDE("/"){
        @Override
        public double apply(double x, double y) {
            return x / y;
        }
    };

    private final String symbol;
    BasicOperation(String symbol){
        this.symbol = symbol;
    }

    @Override
    public String toString() {
        return symbol;
    }
}

在这里,我们重新写了Tip 30 中的四则运算的实现。
他们的区别在于, TIP 30 中枚举类将apply方法声明为abstract, 而此处直接抽象为Operation接口,并让枚举类实现这个接口。


似乎没什么区别啊?
如果我们再增加一个扩展运算类型:

public enum ExtendedOperation implements Operation {
    //求x的y次幂
    EXP("^"){
        @Override
        public double apply(double x, double y) {
            return Math.pow(x, y);
        }
    },
    //求x模y的值
    REMAINDER("%"){
        @Override
        public double apply(double x, double y) {
            return x % y;
        }
    };

    private final String symbol;
    ExtendedOperation(String symbol) {
        this.symbol = symbol;
    }
    @Override
    public String toString() {
        return symbol;
    }
}

想想看, 在任何使用基础运算的地方,都可以使用新的运算,只要将对象类型写成接口类型(Operation)
这就是面向接口编程的特点:

public class OperationTest {
    public static void main(String args[]){
        double x = Double.parseDouble(args[0]);
        double y = Double.parseDouble(args[1]);
        test(ExtendedOperation.class,x ,y);
    }
    private static <T extends Enum<T> & Operation> void test(Class<T> opSet,double x, double y){
        for (Operation op :opSet.getEnumConstants()) {
            System.out.println(x+" "+op+" "+y+" = "+op.apply(x,y));
        }
    }
}

运行一下,看看结果:

cmd->java douvril.effect.OperationTest 2 3
2.0 ^ 3.0 = 8.0
2.0 % 3.0 = 2.0

原书上就是这个例子。豆爷用的是idea,建议直接在终端进入/out目录,使用java命令运行程序。

另外,请仔细品味test方法的泛型使用。ExtendedOperation的字面文字(ExtendedOperation.class)从main传递给了test方法,来描述被扩展操作的集合。这个类的字面文字充当了有限制的类型令牌(TIP 29)。

至于参数 <T extends Enum<T> & Operation> ,则确保了Class对象既表示枚举,又表示Operation的子类型(然而这还是太复杂了)。

下面有另外一个版本:

public class OperationTest2 {
    public static void main(String args[]){
        double x = Double.parseDouble(args[0]);
        double y = Double.parseDouble(args[1]);
        test(Arrays.asList(ExtendedOperation.values()),x ,y);
    }
    private static void test(Collection<? extends  Operation> opSet, double x, double y){
        for (Operation op :opSet) {
            System.out.println(x+" "+op+" "+y+" = "+op.apply(x,y));
        }
    }
}

没有了结构复杂的泛型参数,而且test方法也更加灵活一些:它允许调用者将多个实现类型的操作合并在一起。比如,在调用test时,可以这样:

public class OperationTest2 {
    public static void main(String args[]){
        double x = 3;
        double y = 4;
        List<Operation> operations = new LinkedList<>();
        List<Operation> basicOperations = Arrays.asList(BasicOperation.values());
        List<Operation> extendedOperations = Arrays.asList(ExtendedOperation.values());
        operations.addAll(basicOperations);
        operations.addAll(extendedOperations);
        test(operations,x ,y);
    }
    private static void test(Collection<? extends  Operation> opSet, double x, double y){
        for (Operation op :opSet) {
            System.out.println(x+" "+op+" "+y+" = "+op.apply(x,y));
        }
    }
    /**
     * 错误的示范,这段代码将会抛出一个运行时错误...
     */
    public static void testOp(){
        double x = 3;
        double y = 4;
        List<Operation> operations = Arrays.asList(BasicOperation.values());
        //java.lang.UnsupportedOperationException, 请注意values()方法返回的类型
        operations.addAll(Arrays.asList(ExtendedOperation.values()));
        test(operations,x ,y);
    }
}

运行结果一切正常:

3.0 + 4.0 = 7.0
3.0 - 4.0 = -1.0
3.0 * 4.0 = 12.0
3.0 / 4.0 = 0.75
3.0 ^ 4.0 = 81.0
3.0 % 4.0 = 3.0

这套机制的不足之处,在于toString() 和symbol相关的逻辑代码没法复用,只能一个个编写或拷贝。在这个例子中,此类代码较少而已;如果共同的代码太多,则可以封装在一个静态类或静态辅助方法中,避免代码的复制工作。(参考<<重构-改善既有代码的设计>>, page 76, Duplicated Code相关章节).

总之,虽然无法编写可扩展的枚举类型,却可以通过编写接口以及实现该接口的基础枚举类型,对它进行模拟。这样允许客户端编写自己的枚举来实现接口。如果API是根据接口编写的,那么在任何可以使用基础枚举类型的地方,也都可以使用这些(客户端扩展的)枚举。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值