设计模式必须通过面向对象来实现吗?

阅读数:3842 2013 年 2 月 14 日

话题:设计模式语言 & 开发架构文化 & 方法

陈皓之前为微博上发表了观点:“那 23 个经典的设计模式和 OO(面向对象)半毛钱关系没有,只不过人家用 OO 来实现罢了……OO 的设计模式思想和 Unix 的设计思想基本没什么差别”,结果引来了一点点争议,于是,他在“酷壳”上通过文章说明了自己的观点。

陈皓在文章开头首先举例解释了什么是设计模式:

在正式说明 GoF 的那 23 个经典的设计模式其实和 OO 关系不大并和 Unix 的设计思想很相似的这个观点之前,让我先来说说什么是模式?设计模式的英文是 Design Pattern,模式是 Pattern 的汉译。所谓 Pattern 就是一种规则,或是一种模型,或是一种习惯。Pattern 这个东西到处都是,并不只有技术圏子里才有。

接着,陈皓发表了自己的看法:《设计模式》这本书中,GoF 这四个人总结了 23 个经典的面向对象的设计模式,某中有 5 个创建模式,7 个结构模式,11 个行为模式。很多人都会觉得这是面向对象的设计模式,很多人也觉得非面向对象不能用这些模式。我觉得这是一种教条主义。就像《那些流行的编程方法》中的“设计模式驱动型编程”一样,就像《如此理解面向对象》一样的那么的滑稽。回到我的论点——“GoF 的这 23 个设计模式和 OO 关系不大,并且和 Unix 的设计思想基本一致,只不过 GoF 用 OO 实现了它们”,就像我上面说过的那些生活中的 Pattern 一样,只要你仔细思考,你会发现这 23 个设计模式在我们的生活和社会中也能有他们的身影。而且也一样可以用 OO 的方式实现之。

陈皓针对 23 个经典的设计模式中的几个常用的模式做出了自己的解读。

Factory 模式

这个模式可能是是个人都知道的模式。这个模式在现实社会中就像各种工厂一样,工厂跨界的不多,基本上都是在生产同一类的产品,有的生产汽车,有的生产电视,有的生产衣服,有的生产卫生纸……基本上来说,一个生产线上只有做同一类的东西。这和 Factory 模式很相似。编程中,像内存池、线程池、连接池等池化技术都是这个模式,当然,Factory 给你的一个对象,而不单单只是资源,factory 创建出来的对象都有同样的接口可以被多态调用。这其实和 Unix 把所有的硬件都 factory 成文件一样,并提供了 read/write 等文件操作来让你操作任意设备的 I/O。

Abstract Factory 模式

抽象工厂这个模式是创建一组有同一主题的不同的类。这个模式在现实社会当中也有很多例子,比如:

  • 移动公司的合约机计划,88 套餐(通话 100 分钟,短信 100 条,彩信,20 条,上网 200M),128 套餐(通话 200 分钟,短信 150 条,彩信 50 条,上网 500M)……
  • 家里的装修,总是要有厨卫,有门,有灯,有沙发,有茶几,有床,有衣柜,有电视,有冰箱,有洗衣机……,这些都是必需的,只是每个家庭里的具体装修不一样。
  • Diablo 游戏中的 Normal,Hard,Nightmare,Hell 模式,这些模式的怪和场景和故事情况都差不多,就是每个场景的怪物和装备的属性不一样。或是 WarCraft 中的地图就是一个 Abstract Factory 模式 (注:Warcraft 的地图什么都能干)。这和学校中的小学,初中,高中,大学差不多,都是一样的学习环境,一样的教学方式,一样的教室,都要期中考和期末考,都有班长和科代表,就是学的东西的难度不一样,但基本上都是语文、英语、数、理、化。学校就是一个抽象工厂。

这就是抽象工厂的业务模型(或是:Business Pattern),你觉得是不是不一定非要用 OO 来实现这样的模式?(我们思考一下,我们会不会被先入为主了,觉得不会 OO 都不知道怎么实现了),不用 OO,用相同格式但内容不同的配置文件是不是也能实现?在 Unix 下,抽象工厂这个模式在 Unix 下就像是 /etc/rcX.d 下的那些东西,1 代表命令行单用户,2 代表命令行多用户,3 代表命令行多用户完整模式启动,5 代表图形界面启动,0 代表关机,6 代表重启,你要切换的话,init <X> 就行了。

Prototype 模式

原型模式,复制一个类的实现。这个模式在现实中的例子也有很多:传真,复印,都是这个模式。Unix 进程和 Github 项目的 Fork 就是一种。进程 fork 明显不是 OO 的模型(参看:关于 Fork 的一道面试题)。用非 OO 的方法同样可以实现这个模式。

Singleton 模式

单例模式。生活中,公司只有一个 CEO,法律限制你只能有一个老婆,你只能有一个身份证号,一个 TCP 端口只能被一个进程使用,等等。软件开发方面,并不一定只有 OO 才能做到,你可以用一个全局变量,一个中心服务器,甚至可以使用行政手段来约束开发中不会出现多个实例。Unix 下实现单例进程的一个最常用的实践是在进程启动的时候用“(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)”模式打开一个“锁文件”。

Adapter 模式

适配器模式。可以兼容欧洲美国中国的插头或插座,万能读卡器,可以播放各种格式多媒体文件的插放器,可以解析 FTP/HTTP/HTTPS/ 等网络协议的浏览器,可以兼容各大银行的银联接口、支付宝、Paypal、VISA 等银行接口,可以适配各种后端的解释器的 Nginx 或 Apache,等等。用非 OO 的编程方式就是重新包装成一个标准接口。这个模式很像 Unix 下的 /dev 下的那些文件,操作系统把系统设备适配成文件,于是你就可以使用 read/write 来进行读写了。

Bridge 模式

桥接模式。这个模式用的更多,比如一个灯具可以接各种灯泡或灯管,一个电钻可以换上不同的钻头来适应不同的材料,一辆汽车可以随时更换不同的轮胎来适应不同的路面,你的桌面可以随时更换一个图片来适应你的心情,你的单反相机可以更换不同的镜头来拍不同的照片…… 桥接模式说白了就是组件化,模块化,可以自由拼装。在 OO 中,其主要是通过让业务类组合一个标准接口来完成,这在非 OO 的程序设计中用得实在是太多了,主要是通过回调函数或是标准接口来实现。这个也是 Unix 设计哲学中的主要思想。在 Unix 中,文件的权限使用的就是 Bridge 模式,标准接口是用户,用户组和其它,rwx 三个模式,然后用 chmod/chown 改一改,这文件就有不同的属主和属性了。

Decorator 模式。

装饰模式。这个模式在生活中太多了,你给你的手机或电脑贴个什么,挂个什么,吃东西的时候加点什么佐料,多点肉还是多个蛋,一个 Unix/Linux 命令的各种参数是对这个命令的修饰,等等。我觉得这个模式在 Unix 中最经常的体现就是通过管道把命令连接起来来完成一个功能,比如:ps -elf 是列进程的,用管道 grep hchen 就可以达到过滤的目的,grep 的逻辑没有侵入 ps 中,grep 修饰了 ps,但是其组合起来完成了一个特定的功能。可见,这和 OO 没有什么关系。

Facade 模式

这个模式我们每个人从会编程的时候就在无意识地用这个模式了。这个模式就是把一大堆类拼装起来,并统一往外提供提口。在现实生活中这样的例子太多了,比如:旅行社把机票,酒店,景点,导游,司机,进店打了一个包叫旅行;IBM 把主机、存储、OS、J2EE、DB、网络、流程打了个包叫企业级解决方案。Unix 中最典型的一个例子就是用 Shell 脚本组合各种命令来创造一个新的功能,这是的 Shell 中的各种命令通过标准 I/O 这个接口进行组合交互。

此外,陈皓还讨论了Proxy 模式Chain of Responsibility 模式Command 模式Observer 模式Strategy 模式,读者可以查看原文

最后,陈皓总结道:我们会发现上面 OO 搞出来的那么多模式在 Unix 下看来好像没有那么复杂,而且 Unix 下看起来并没有那么多模式,而且 Unix 中的设计模式无非就是这么几个关键词:单一,简洁,模块,拼装。我们再来看看 OO 设计的两大准则:1)钟情于组合而不是继承,2)依赖于接口而不是实现。你看,Unix 和 OO 设计模式是不是完美的统一吗?