这篇文章缘起于前几天微博上有关动态语言与静态语言的讨论,因为有几个编程高手参加,所以能看到一些特别有启发性的发言。本文主要是下面这一条微博的读后感,也是我的练习与思考。
关于动态语言与静态语言,有很多比较和讨论它们的文章,但大部分都没有抓住重点。而上面一条微博,提到了一个很好的切入点,那就是「状态模式(State Pattern)」。
状态模式:蝙蝠侠/布鲁斯·韦恩
蝙蝠侠(英语:Batman)是一名出现于DC漫画的虚构超级英雄角色,由鲍勃·凯恩和比尔·芬格创作。他的名字叫 Bruce,是一位美国亿万富翁,这是他的正常身份,用于正常生活,例如进行参加宴会之类的活动。他的另一个身份是 Batman,是打击犯罪的黑暗骑士。
这是一个状态模式的好示例,我用 Java 和 JavaScript 各写了一个示例,体会体会「极其繁琐」与「非常简单」。
极其繁琐:Java 版本
目录结构:
com
|-- tianfangye
|-- Client.java
|-- person
|-- Person.java
|-- state
|-- BatmanState.java
|-- BruceState.java
|-- IState.java
Person.java
1 | package com.tianfangye.person; |
IState.java
1 | package com.tianfangye.state; |
BruceState.java
1 | package com.tianfangye.state; |
BatmanState.java
1 | package com.tianfangye.state; |
Client.java
1 | package com.tianfangye; |
程序输出:
- Bruce - <> 参加宴会
- Batman - <> 打击犯罪
关于状态模式的实现,GoF 的 Design Patterns 里面提到过一些需要考虑的方面,其中之一是「谁来定义状态的转换」。可以由状态的使用者(Person)实现,也可以由每个状态各自实现,各有利弊。上例由每个状态各自实现,接下来的 JavaScript 示例也是这种选择(对于示例程序,另一种实现更简单)。
非常简单:JavaScript 版本
1 | const state = { |
程序输出:
- Bruce - <> 参加宴会
- Batman - <> 打击犯罪
可以看到,相比静态语言的版本,动态语言的版本竟是如此浑然天成!甚至不是在实现「设计模式」,只是对语言特性的正常使用而已。所以,什么是设计模式?聪明人应该想通了——设计模式是一个衍生问题,不是本质问题。本质问题是程序设计,是静态语言与动态语言,是静态类型与动态类型,是编程语言抽象。
@vczh的一个知乎回答对这一点讲得很透彻:
显而易见,类型是个大问题!动态语言能做到非常简洁是由于动态类型系统的灵活性。它们在相当大的程度上简化了程度设计,也就是所谓的「非常简单」。要明白,这里的「简单」与「复杂」并不是指代码量的多少,也不是指语言特性使用的多少,而是指花费在程序设计上面的心思的多少。具体到本例,主要是下面两点:
- 动态类型:不需要花心思去搞定类型检查。
- 运行时绑定上下文的 this 对象:不需要将有状态的对象传来传去。
当然,这些灵活性并非没有代价(性能)。另外,关于动态语言与静态语言的选择,一直有很多工程问题上的争议(从文章开头的微博往下探索,可以找到大段这方面的争论)。我个人对于动态语言、静态语言没有明显偏好,只是喜欢把问题弄清楚——不论有没有偏好,偏好强烈还是微弱,这都是首要的一步。