无论是学术界还是工业界,已经有越来越多的人对设计模式持反对的态度。他们认为,设计模式是有害的 (Design patterns should be considered harmful)。 如果你读过所谓“精通 Java 设计模式”之流的书,并仔细思考其中的“设计理念”,你会发现,其实很多设计模式是语言设计的缺陷导致的。 反观其他一些编程语言,相当一部分设计模式在它们之中并不存在。为什么?因为根本不需要,只要正常写就行了。而访问者模式 (visitor pattern) 就是其中之一。
sealed abstract class Expr
case class Add(lhs: Expr, rhs: Expr) extends Expr
case class Sub(lhs: Expr, rhs: Expr) extends Expr
case class Number(value: Int) extends Expr
def eval(expr: Expr): Int = expr match {
case Add(l, r) => eval(l) + eval(r)
case Sub(l, r) => eval(l) - eval(r)
case Number(v) => v
}
简单易懂,易于调试。如果你漏掉了一些 case,如忘记了 Number:
def eval(expr: Expr): Int = expr match {
case Add(l, r) => eval(l) + eval(r)
case Sub(l, r) => eval(l) - eval(r)
}
编译器还会报警告:
warning: match may not be exhaustive.
It would fail on the following input: Number(_)
def eval(expr: Expr): Int = expr match {
^
你如果没看见,那么运行时求值到 Number 时也会抛出异常 MatchError,这样你很快就能意识到哪里出错了。
abstract class Expr {}
class Add extends Expr { Expr lhs; Expr rhs; }
class Sub extends Expr { Expr lhs; Expr rhs; }
class Number extends Expr { int value; }
int eval(Expr expr) {
if (expr instanceof Add) { // 分支1
var e = (Add) expr;
var l = eval(e.lhs);
var r = eval(e.rhs);
return l + r;
} else if (expr instanceof Sub) { // 分支2
var e = (Sub) expr;
var l = eval(e.lhs);
var r = eval(e.rhs);
return l - r;
} else { // 分支3:expr instanceof Number
var e = (Number) expr;
return e.value;
}
}
abstract class Expr {
abstract <T> T accept(ExprVisitor<T> v);
}
class Add extends Expr {
Expr lhs;
Expr rhs;
@Override
<T> T accept(ExprVisitor<T> v) { return v.visitAdd(this); }
}
class Sub extends Expr {
Expr lhs;
Expr rhs;
@Override
<T> T accept(ExprVisitor<T> v) { return v.visitSub(this); }
}
class Number extends Expr {
int value;
@Override
<T> T accept(ExprVisitor<T> v) { return v.visitNumber(this); }
}
最后将 eval 方法实现为一个访问者的实例:
class EvalVisitor implements ExprVisitor<Integer> {
@Override
int visitAdd(Add e) {
var l = e.lhs.accept(this);
var r = e.rhs.accept(this);
return l + r;
}
@Override
int visitSub(Sub e) {
var l = e.lhs.accept(this);
var r = e.rhs.accept(this);
return l - r;
}
@Override
int visitNumber(Number e) {
return e.value;
}
}
int eval(Expr expr) {
var v = new EvalVisitor();
return expr.accept(v);
}