迭代1:快速实现
需求1:实现一个计算器,完成加减乘除运算
public final class Operations {
public final static int PLUS = 0;
public final static int MINUS = 1;
public final static int TIMES = 2;
public final static int DIVIDE = 3;
public static int apply(int op, int x, int y) {
switch (op) {
case PLUS: return x + y;
case MINUS: return x - y;
case TIMES: return x * y;
case DIVIDE: return x / y;
}
throw new AssertionError("Unknown op: " + op);
}
private Operations() {
}
}
这是一个很糟糕的设计,存在很多的坏味道。
类型不安全
一般地,用户按照规则传递正确的op
常量。
import static cn.codingstyle.Operations.*;
assertThat(apply(PLUS, 1, 1), equalTo(2));
但不排除用户传递错误的op
值,导致运行时失败,抛出AssertionError
。
int op = ...
assertThat(apply(op, 1, 1), equalTo(2));
违背OCP原则
对于增加新的操作类型,或者增加行为,将导致大量的重复代码,给后期维护带来很大的伤害。
需求2:输出操作符的字符串表示
可以增加operator
方法,用于输出操作符表示的字符串,设计存在大量重复。
public static int apply(int op, int x, int y) {
switch (op) {
case PLUS: return x + y;
case MINUS: return x - y;
case TIMES: return x * y;
case DIVIDE: return x / y;
}
throw new AssertionError("Unknown op: " + op);
}
public static String operator(int op) {
switch (op) {
case PLUS: return "+";
case MINUS: return "-";
case TIMES: return "*";
case DIVIDE: return "/";
}
throw new AssertionError("Unknown op: " + op);
}
需求3:支持求模运算
每况愈下,如果再增加一个求模数的操作类型,则需要散弹式地修改程序。
public static int apply(int op, int x, int y) {
switch (op) {
case PLUS: return x + y;
case MINUS: return x - y;
case TIMES: return x * y;
case DIVIDE: return x / y;
case MOD: return x % y;
}
throw new AssertionError("Unknown op: " + op);
}
public static String operator(int op) {
switch (op) {
case PLUS: return "+";
case MINUS: return "-";
case TIMES: return "*";
case DIVIDE: return "/";
case MOD: return "%";
}
throw new AssertionError("Unknown op: " + op);
}
当需求变化时,应遵循良好的正交设计原则,避免做乘法,只做加法,甚至是减法。
迭代2:类型安全
为了使得类型安全,约束用户行为,可以将常量定义为强类型的枚举。
public enum Operation {
PLUS, MINUS, TIMES, DIVIDE, MOD;
}
重构是一个高度危险的实践活动,步子越小越安全。
Operations
的apply, operator
方法类型约束得到了改善,但实现依然充斥坏味道。
public final class Operations {
public static int apply(Operation op, int x, int y) {
switch (op) {
case PLUS: return x + y;
case MINUS: return x - y;
case TIMES: return x * y;
case DIVIDE: return x / y;
case MOD: return x % y;
}
throw new AssertionError("Unknown op: " + op);
}
public static String operator(Operation op) {
switch (op) {
case PLUS: return "+";
case MINUS: return "-";
case TIMES: return "*";
case DIVIDE: return "/";
case MOD: return "%";
}
throw new AssertionError("Unknown op: " + op);
}
private Operations() {
}
}
迭代3:引入多态
将Operation
重构为具有多态能力枚举类,遵循了OCP
良好的设计原则,使得程序更加具有弹性,但存在较多的「语法噪声」。
public enum Operation {
PLUS("+") {
@Override
public int apply(int x, int y) {
return x + y;
}
},
MINUS("-") {
@Override
public int apply(int x, int y) {
return x - y;
}
},
TIMES("*") {
@Override
public int apply(int x, int y) {
return x * y;
}
},
DIVIDE("/") {
@Override
public int apply(int x, int y) {
return x / y;
}
};
MOD("%") {
@Override
public int apply(int x, int y) {
return x % y;
}
};
public abstract int apply(int x, int y);
public String operator() {
return op;
}
private Operation(String op) {
this.op = op;
}
private String op;
}
迭代4:消除噪声
为了进一步消除语法噪声,可以使用Java8 Lambda
进一步改善结构,使得成设计更加直观明了。
import java.util.function.BinaryOperator;
public enum Operation {
PLUS("+", (x, y) -> x + y),
MINUS("-", (x, y) -> x - y),
TIMES("*", (x, y) -> x * y),
DIVIDE("/", (x, y) -> x / y),
MOD("%", (x, y) -> x % y);
public int apply(int x, int y) {
return f.apply(x, y);
}
public String operator() {
return operator;
}
private Operation(String operator, BinaryOperator<Integer> f) {
this.operator = operator;
this.f = f;
}
private BinaryOperator<Integer> f;
private String operator;
}
迭代5:使用Scala
使用Scala
,可进一步将语法噪声消除,并让用户接口更加人性化,程序更加简单,漂亮。
sealed trait Operation(val operator: String,
op: (Int, Int) => Int) extends ((Int, Int) => Int) {
override def apply(x: Int, y: Int) = op(x, y)
}
case object plus extends Operation("+", _ + _)
case object minus extends Operation("-", _ - _)
case object times extends Operation("*", _ * _)
case object divide extends Operation("/", _ / _)
case object mod extends Operation("%", _ % _)
约束表达设计
sealed
明确地声明,外部不能再对Operation
进行扩展了,不仅仅使得「模式匹配」成为可能,更重要的是传到了作者设计的意图,并在编译时约束用户的行为。
同样地,final
修饰case object
,也是出于同样的目的。
混血儿
(Int, Int) => Int)
等价于Function2[Int, Int, Int]
,而Operation
重写了Function2.apply
方法,当用户使用plus, minus, times, divide
时,其表现得更像函数的行为。
scala > plus(1, 1)
res0: Int = 2
当调用operator
方法时,又散发出OO
的气息,Scala
就是一个与众不同的「混血儿」。
scala > plus.operator
res1: String = +
网友评论
Calculator.apply(x, y)(Opertion.add)
定义如下:
object Calculator {
def apply(x: Int, y: Int)(f: (Int, Int) => Int) { f(x, y) }
}
object Operator {
val add = (x, y) => x + y
val minus = (x,y) => x - y
}