本章简介
本章主要介绍编译器中最主要的设计模式,观察者模式,这里我无法讲述观察者模式的理论或者是用UML画类图等等,只想就这个项目,说说该如何运用观察者模式。
观察者模式
观察者模式举例
以本项目为基础,结合个人理解,观察者模式具有下面的定义
观察的对象只有一个,而观察者有多个,每个观察者在对观察对象进行观察以后,可以执行不同的操作。
这里就可以简单的举两个观察者的例子,最基本的例子就是如何对程序进行树状打印,如下例子
(+ 1 (* 2 3) 4)
树状打印之后的输出有如下的形式
op:+
1
op:*
2
3
4
而对表达式计算的观察者,有如下的顺序:
- 看见(
,进行第一层的观察
- 看见+
, 记录
- 看见1
, 记录
- 看见(
, 进行第二层的观察
- 看见2
,记录
- 看见3
,记录
- 看见)
,本层分析完毕,进行计算,结果为6
- 看见4
,记录
- 看见)
,本层分析完毕,进行计算,结果为11
观察者模式详细分析
本项目中,设计了接口Execuable
,代码如下:
public interface Executable {
SchemeToken accept(Executor executor);
}
同时设计了观察者类Executor
public class Executor {
...
/**
* the execution part
*/
public SchemeToken execute(SchemeToken Token) {
...
}
public SchemeToken execute(SchemeList Token) {
...
}
public SchemeNumber execute(SchemeArithmetic expr) {
...
}
public SchemeToken execute(SchemeDefine expr) {
...
}
public SchemeBoolean execute(SchemeStringCompare expr) {
...
}
public SchemeBoolean execute(SchemeNumberCompare expr) {
...
}
public SchemeToken execute(SchemeCall expr) {
...
}
public SchemeToken execute(SchemeCond expr) {
...
}
public SchemeToken execute(SchemeIf expr) {
...
}
public SchemeToken execute(SchemeApply expr) {
...
}
public SchemeToken execute(SchemeAtom expr) {
return TopScope().GetValue(expr.getContent());
}
public SchemeString execute(SchemeString expr) {
return expr;
}
public SchemeBoolean execute(SchemeBoolean expr) {
return expr;
}
public SchemeNumber execute(SchemeNumber expr) {
return expr;
}
public SchemeVoid execute(SchemeVoid expr) { return expr; }
public SchemeToken execute(SchemeQuoted expr) {
return expr.getContent();
}
}
此时我们可以让SchemeToken类来实现Execuable接口,实现了这个接口,用白话文解释就是,SchemeToken变成可执行的对象了,而Executor(执行者)只能执行Execuable(可执行的)对象。这句话可能听起来比较拗口,但是我觉得这是一种比较好理解的方式。我还是复述一次
Executor只能执行Execuable的对象
这里可以想象程序执行的时候有一段对话。
如果执行的程序是
(define f (lambda (x y) (+ x y)))
(apply f '(1 2 3 4)) => 观察对象
起始观察对象为SchemeApply
Executor正在执行(观察)整个表达式,可是本次的执行(观察)并不能得出结果,因为此时apply的函数和后面的apply的对象在Executor看来是两个Token,所以,Executor按照顺序对这两个Token说:“我这里有全部对象的执行(观察)步骤,可是我不知道你是谁,那我先把我自己当作参数传递给你,然后,你使用我进行你自己的的计算吧,就和我计算SchemeApply一样!”
第二个参数说:“好,此时我是一个Atom,开始计算,我的计算步骤很简单,就是获取运行时所绑定的对象,,,,计算完毕,此时我也不知道我绑定的是什么对象,我获取到的也是一个Token,我先传递给你(Executor)吧!”
Executor说:“好的,我已经获取到了第二个参数的计算结果,此时我进行合法性的判断,,,,合法,这个对象在运行时是一个SchemeProcedure
,并且具有两个参数”(SchemeApply
的第三个参数和第二个参数的过程类似)
Executor说:“好,此时后面两个参数都已经计算出结果了,这个时候开始计算,进行reduce,结果出来了,为10,执行(观察)完毕。”
这里我再设计一段代码,并且附上流程图,流程图分析的是最后一句代码,其中每一个节点是一个CallSite
(define a (lambda (x y) (+ x y)))
(define f '(1 2 3 4))
(apply a f)
结语
本篇博客我自认为写的一般般,同时我也尽了自己最大的努力使其变得书面化,但是我自认为设计模式这个东西,理论实在是太枯燥,所以如果大家想更好的理解观察者模式在编译器构造中的使用,可以看我的第一篇博客中的码云地址,里面有详细的代码,或者是可以买一本书《自制编译器》,作者是青木峰郎,里面有详细的解释,非常的全面,也不难懂。同时这里希望我的每一篇博客能够让想学编译的同学更快地入门,发现其中的乐趣。
下一章内容,有毒的函数式编程。