geek青年的状态机,查表,纯C语言实现
1. 问题的提出,抽象
建一,不止是他,不少人跟我讨论过这样的问题:如何才能保证在需求变更、扩充的情况下,程序的主体部分不动呢?
这是一个非常深刻和艰难的问题。在进入实质讨论之前,我们还得先明确什么是"主体",就是我们不希望动的那一部分是什么。事实上,没有什么"主体",这是被我们主观划分的,代码中有一部分是不动的,另一部分是动的。而追求永恒(一劳永逸?) ,是我们的天性吧。
我们希望实现一段程序,换一些东西,游戏就由 双截龙 变成了 超级玛丽,再换一点东西,就变成了 魂斗罗。只要招些美工,再招些脚本作者,所有的程序员就可以--解雇了。
这看起来不太现实,那么我们来看一段类似的,但是更现实一点的。我们希望实现一段程序,在每轮迭代/循环中,这段代码都能完成我们需要做的任务,虽然这些任务可能在每轮迭代中有所不同。在数学归纳法,在 sigma 符号的的周围,甚至在积分符号的周围,都在发生这样的事情。
这些梦想或者已经实现的技术,都基于"抽象"。我们试图找到在不同的情境 (动作、需求) 下那些相同的部分。我们对具体事件做抽象,并且期待抽象的结果适用于所有的具体的事例。这样,原来的很多工作就成为 应用抽象的理论 的过程,不再需要创造力,因此也不再能吸引我们。那么,我们再对抽象的结果继续抽象,直到形而上。
2. 状态机的引擎
引擎,就是上文中提到的开发出一个游戏,然后能衍生出很多游戏的技术。代码的核心部分、流程部分不会改变,只有数据 (甚至可以在外部文件中) 才随需求的变化而变化。
状态机,也可以用引擎实现。实现这一目标的技术也存在已久,就是查表。查表的经典案例是 求三角函数 (在一定精度下),常量时间复杂度的解决方案 就是查表。事先把三角函数在不同度数下的值都求出来,放在hash表 (?) 里。你要查哪个度数,我就去查哪个度数对应的函数值。
在这个案例里,查表的那段代码,不随三角函数由sin变成cos或tan而发生任何变化。这就是引擎,被查的表就是数据。
3. 接口
我们期待的接口跟前一篇普通青年中的完全一样。在主函数中调用 void state_change(enum message m) 向状态机传递消息,用 test.in 作为测试用例。主函数还知道,一共就这样几种消息:
4. 状态迁移表
在讲如何查表前,我们先设计 表 本身。我们期待表格能够描述 状态迁移 中的要素。记得么,一共4个。 (1) 当前状态, (2)当前消息, (3)将迁移到的状态,(4)在状态迁移中的动作。我们期待能用表格,而不是如普通青年一文中用代码(switch-case)的方式描述。因为我们相信,改表格比改代码容易。
1. 问题的提出,抽象
建一,不止是他,不少人跟我讨论过这样的问题:如何才能保证在需求变更、扩充的情况下,程序的主体部分不动呢?
这是一个非常深刻和艰难的问题。在进入实质讨论之前,我们还得先明确什么是"主体",就是我们不希望动的那一部分是什么。事实上,没有什么"主体",这是被我们主观划分的,代码中有一部分是不动的,另一部分是动的。而追求永恒(一劳永逸?) ,是我们的天性吧。
我们希望实现一段程序,换一些东西,游戏就由 双截龙 变成了 超级玛丽,再换一点东西,就变成了 魂斗罗。只要招些美工,再招些脚本作者,所有的程序员就可以--解雇了。
这看起来不太现实,那么我们来看一段类似的,但是更现实一点的。我们希望实现一段程序,在每轮迭代/循环中,这段代码都能完成我们需要做的任务,虽然这些任务可能在每轮迭代中有所不同。在数学归纳法,在 sigma 符号的的周围,甚至在积分符号的周围,都在发生这样的事情。
这些梦想或者已经实现的技术,都基于"抽象"。我们试图找到在不同的情境 (动作、需求) 下那些相同的部分。我们对具体事件做抽象,并且期待抽象的结果适用于所有的具体的事例。这样,原来的很多工作就成为 应用抽象的理论 的过程,不再需要创造力,因此也不再能吸引我们。那么,我们再对抽象的结果继续抽象,直到形而上。
2. 状态机的引擎
引擎,就是上文中提到的开发出一个游戏,然后能衍生出很多游戏的技术。代码的核心部分、流程部分不会改变,只有数据 (甚至可以在外部文件中) 才随需求的变化而变化。
状态机,也可以用引擎实现。实现这一目标的技术也存在已久,就是查表。查表的经典案例是 求三角函数 (在一定精度下),常量时间复杂度的解决方案 就是查表。事先把三角函数在不同度数下的值都求出来,放在hash表 (?) 里。你要查哪个度数,我就去查哪个度数对应的函数值。
在这个案例里,查表的那段代码,不随三角函数由sin变成cos或tan而发生任何变化。这就是引擎,被查的表就是数据。
3. 接口
我们期待的接口跟前一篇普通青年中的完全一样。在主函数中调用 void state_change(enum message m) 向状态机传递消息,用 test.in 作为测试用例。主函数还知道,一共就这样几种消息:
enum message { play, stop, forward, backward, record, pause };
4. 状态迁移表
在讲如何查表前,我们先设计 表 本身。我们期待表格能够描述 状态迁移 中的要素。记得么,一共4个。 (1) 当前状态, (2)当前消息, (3)将迁移到的状态,(4)在状态迁移中的动作。我们期待能用表格,而不是如普通青年一文中用代码(switch-case)的方式描述。因为我们相信,改表格比改代码容易。
状态迁移表与状态迁移图完全等价。