(明明学会了这个模式啊,可为什么用不起来?《把设计模式用起来》—— 这是我和清华大学出版社签约的书稿预览)
第1章 为什么用不好设计模式?
罗伯特.博伊德(美国加利福尼亚大学人类学系教授)说过,模仿是人类最常用的学习方式。只是有些知识或能力,仅靠模仿就能掌握十之八九,比如学写汉字;而有些,光靠模仿就只能入个门甚至学个像,比如写文章。
有人说,不对啊,网上到处有神奇的网友,模仿《孔乙己》的文笔再结合时事,不也写出了惟妙惟肖的桥段?不,那只是模仿了行文的句式和惯用的语气,哪个段子手敢因此说自己的写作能力堪比鲁迅?
类似,照着书里的样例代码、配合被“设计出来”的案例,读者很快就能写出某个设计模式,但到了实际项目,发现学过的模式用不上、用不对、用不好,这样真的算学会设计模式了吗?
设计模式是知识也是能力,模仿易,应用难,因此,如果我能把设计模式应用难的原因找出来,说明白,一定对读者大有裨益。
小提示:一些代词的解释
行文中会有大量“你”、“你们”、“我”、“我们”用来指代自觉还没有学好设计模式的读者,有时也会称作“程序员新人”;不管怎样,它们都不是用来指代某个具体的读者。
一、实践不足
写不出《阿Q正传》,不是因为我们文笔不好,而是因为“赵太爷”被打倒了。想要领悟一些深刻的认知,往往你得先有过一些深刻的经历。比如,失恋的痛,没有恋爱过的人不会懂。
“希望是本无所谓有,无所谓无的。这正如地上的路,其实地上本没有路,走的人多了,也便成了路”。一个人只要善于模仿,就可以通过套用“……本无所谓有,无所谓无……正如……多了,也便成了……”的语式,造出一堆句子,但这并不代表他的写作能力有多大提升。
小提示:那怎样才算学到了?
夜。少年读过鲁迅文章的他,推开出租屋的半户窗,楼下街巷阡陌,摆摊的人在看手机。楼上,他的手机屏幕也亮起,一条是面试被拒的通知,另一条也是。
“地上本来有很多路,只是走的人多了,也就没有了路”,他发了一条朋友圈。
“每一个模式描述了一类在我们周围不断重复发生的问题……”(Christopher Alexander,建筑学家,加州大学伯克利分校的终身教授)。
你说你在学习设计模式,那么回答我,上次遇到的严重困扰你的设计问题,是哪个?
“啊,让我想想,好像是说我有一个老婆,她爱打扮也爱买包包……”
不开玩笑,很多同学不管是在面试时,还是在技术沙龙中,提到设计模式的例子,开口就是网上一搜一大把的各种虚拟案例,让他举个自己碰到的,立马不举了。
猜到我要说什么了吗?
我们学不好设计模式,不是因为我们不聪明,也不是因为我们不用功,很大可能是因为我们并没遇到多少问题。意识到这一点,我们首先得到一条选书指南:讲设计模式的书或网文,如果书中只有一眼假的案例,就不要选它。没有人愿意在“楚门的世界”里成长。
是时候再次强调下, 《设计模式:可复用面向对象软件的基础》是一本经典,书中大多数案例都非常真实。
表 1 《设计模式:可复用面向对象软件的基础》案例举例
模式 | 问题(基于阅读理解所写,非原文) |
复合模式 (Composite) | 图元包括线、框、文本、图形框,其中图形框是一个容器,它可以包含线、框、文本以及另一个图形框,代码要怎么表达这种递归包含关系才方便统一处理? |
抽象工厂模式 (Abstract Factory) | 软件需支持多种运行环境(比如操作系统),不同环境下,软件窗口、按钮、滚动条等图元组件有成套的不同外观和行为,如何控制这种变化的影响范围? |
生成器模式 (Builder) | 阅读器使用RTF作为展现文档源格式,现在希望阅读器能将源文档转换成数目不断增加的目标格式,不同目标格式所能兼容的源格式子集也不同,如何做到阅读器用以遍历文档的代码,无需改动,就能支持现有和未来的多种转换目标?(注:Lexi 可能并未实际使用该模式) |
策略模式 (Strategy) | 正文段落的文字如何换行?这事不简单,它有专门的国家标准。字处理软件的开发者不可能懂各国的标准,如果在不改动排版代码的前提下,让软件可以方便地更换换行算法? |
装饰器模式 (Decorator) | 段落中有一个文本框,它懂得如何排版其内的文图内容,但不懂得在内容超长时,如何实现滚动;幸好我们也已经有一个滚动框,如何将前者嵌入后者,并且无需让所在的段落知道它所拥有的框已经从文本框变成滚动框? |
享元模式 (Flyweight) | 字处理软件打开万字文档,为每个字生成一个对象以存储它的位置、字体、大小等信息很费内存,如何改善?(注:Lexi 可能并未实际使用该模式) |
命令模式 (Command) | 字处理软件提供菜单项、工具图标等典型的GUI操作入口,如何实现不同入口的请求可触发相同响应?如何支持多级撤消请求? |
(我猜,你并没有逐字逐句看完上表内容?不要紧,请继续往下看。)
表中的问题都依托一个字处理软件(原书称为“所见即所得的文档编辑器”)的案例而存在。这个字处理软件甚至有一个名字叫“Lexi”,它来自另一本书,1990年的《The object-oriented implementation of a document editor》中的案例。
我喜欢《设计模式:可复用面向对象软件的基础》的原因之一,是正好写过一段时间的字处理软件,“Lexi”需要面对的,如何跨平台,如何支持多种观感,或者打开大文档如何节省运行开销等问题,我都经历过,所以读起来相对轻松,甚至有亲切感。但是,更多程序员没有这类经历,阅读书中大段有关字处理软件业务的文字,自然有额外阻力。
这就是讲设计模式的书或文章不喜欢过多使用真实项目作案例的主要原因:不希望读者在学习模式的同时,还需要理解来自特定项目的,特定领域的业务逻辑。
书里找不到,那么以赛代练,在实际工作中寻找如何?作为工作30多年的过来人,我给的结论有点残酷:很多情况下你在工作中也找不到合适的案例,比如:
- 情况A,你是编程新人,能力有限,主管给你的工作多是边边角角的杂活,没有多少使用设计模式的需要;
- 情况B,你能干不少活,但你的活越来越多,多到你天天加班忙不完,多到你根本没有时间去思考如何改善代码;
- 情况C,领导总给你重复的、相似的开发工作;个别企业里,你甚至长期只需做一些“填充题”即可;
- 情况D,你所要解决的问题,本就不太需要设计模式,比如:a. 算法岗,特别是偏数学领域的算法,b. 底层基础功能库实现,c. 验证性编程(写代码验证特定功能的可行性,或获取功能的基本性能数据,此类代码经常是一次性的)。
四种情况都涉及到工作安排,仅有情况B的领导是坏蛋,其他三种都合情合理。也就是说,新人遭遇这几种情况作为开局,算是常见的。
有时候,强大的开发框架也会友好地帮你拦下劲敌。举个例子,许多文章教我们如何用单例模式实现程序中数据库连接。可等我们进项目组,组长打开一个配置文件,指向某个位置:“看好了,在这里填个‘singleton’……”。没错,做个简单的配置,就可以在代码中取到连接单例。这对开发是好事,毕竟,数据库连接这么重要,怎么可以交给程序员手写?我们因此错过了一场遭遇战,尽管可以去阅读框架的源代码,去理解、去仿写……仿佛是一个少年,在军事博物馆查看前辈的战利品,浮想联翩。
说到军事,我们正好说个比喻:项目是战役,代码是兵,模式是阵式,而你,程序员是指挥官。学习打战如何排兵布阵,仅靠熟读兵书难成大器,还得多多实战才能所有积累。
再者,一场战役中,岗位或任务不同,积累的经验也不尽相同,一线上当班长带兵杀敌?还是在炊事班里张罗饭菜?差别很大。
最后,你必须拥有时间思考,否则就永远是“被指挥”而不是真正的指挥官。很多时候,表面上你确实在噼噼啪啪地“创造”代码,但其实在高压下,人类反倒会本能地使用最简单的,最不计成本的方式完成手头工作。当一个程序员被逼到只能机械地工作时,“复制/粘贴”而得的代码,已经是最好的设计模式。
这就是本小节的结论 “脱离实践,很难学好设计模式”。相信你不会反对这个观点,但是,我知道,你的内心,还会些疑惑,让我来推测一下问题是什么,并且给回答。
- 可能问题1:实践带来什么,能让一个程序员真正学会设计模式?
答:实践能更快地训练我们的分析能力。
通过实践,我们遭遇问题,加以分析,使用模式;或者,我们基于分析,发现问题,使用模式。缺少实践,我们听别人讲一个模式,再看他造一个问题,最后分析一番,典型的看着答案分析问题——只要你当过学生,我相信你一定懂得真正靠自己解答出一个问题,和偷偷地翻看书后的标准答案,再给出解题思路这两种学习方法之间的重大区别(当然,我并没有否定后面那种方法也能带来一定的学习成效)。
- 可能问题2:只有工作才算实践吗?没工作的人怎么办?
工作,惟有工作才能给程序员制造出量大管饱、五花八门外加变幻莫测的问题;也只有工作,才能提供下述助力:
- 善变的甲方,帮你出题——在如何把业务对象搞复杂这件事上,再强的院校老师、行业前辈、书籍作者都不如项目甲方刁钻;
- 紧迫的条件,催你交卷——选择设计模式并非纯技术推敲,工期、成本、市场、运行环境甚至竞争对手,都是得出最优解需要考虑的;
- 挑剔的同行,对你评判——将代码交给同事使用,甚至开源给公众使用,和写着“演示”代码自娱自乐,前者更能让你成长;
- 有收入——收入是动力,带薪学习更长远;收入也是一种价值体现,如果学了设计模式并没有让你收入增多,除了老板的问题,也要有所警惕:莫非我学的是假的设计模式?
没有工作,或者有工作,但并未被工作逼迫使用设计模式的人,对设计模式的使用容易停留在纸上谈兵的水平。
如此一说,那没有工作,比如还在读大学的同学怎么办呢?是不是干脆在上班之前就不要学习设计模式了?当然不是!像《设计模式:可复用面向对象软件的基础》的作者那样能够“发明”(本质是发现)23种设计模式的程序员,既需要天赋,更需要长期的编程工作历练。作为后来者,我们当然值得先从模式自身学起。
另外,如前所述,像《设计模式:可复用面向对象软件的基础》这样的经典,所给的大部分案例极为真实,因此阅读类似书籍,一定能帮助我们深刻理解设计模式,并朝着学以致用的方向迈出关键的第一步。可惜,《设计模式:可复用面向对象软件的基础》的主要案例来自字处理软件,因此带来一些额外的阅读困难。
小提示:字处理软件的业务逻辑难在哪里?
字处理软件,比如微软“Office套件”中的“Word”,比如金山办公软件中的“文字”。按理每个程序员都用过,并不陌生,为什么还会觉得难以理解呢?这个问题详谈起来,会是另外一个长长的话题,这里仅作简单解释。
- 最主要的原因:相比多数软件系统,字处理软件的抽象程度会更高;
- 该书中用于制作问题的不少需求,也是字处理的日常用户很少关注的功能点,比如“支持多种窗口系统”、“支持多种视感标准”;
- 现代的程序员多数对WEB端应用的相关机制更加熟悉,而文中的字处理软件显然是一款的传统桌面端应用,对缺少GUI(图形用户界面)应用编程经验的程序员,面对诸如“XXXView”、“XXXShape”、“XXXDisplay”之类的命名,经常需要大脑短暂卡顿一下才能反应过来彼此的区别;
- 由于时代差异(对中文用户来说,也有翻译的原因),书中的一些术语相对陌生,比如“图元(Glyph)”、“围栏(Enclosure)”、“窗口组件(Widget)”、“视感(look-and-feel)”等等。
如果有一本讲设计模式的书,它的用例来源于当今社会所有人,哪怕不是程序员都非常非常熟悉的业务领域,那该有多好啊……
- 可能的问题3:所以,那些虚拟案例就毫无价值吗?
一些虚拟案例对读者提升问题分析能力,会有那么“一丢丢”的帮助,更多的虚拟案例为了弱化理解难度,突显服务特定模式的目的,做了很多加工,仿佛一张原始图片被加上“极度简化”和“过度设计”两道滤镜,变得严重失真,脱离实际,基于这样的案例来来训练分析能力,容易适得其反。
不过,虚拟案例并非毫无作用,好的虚拟案例对用快速理解模式帮助极大,是真实案例难以轻松做到的。(当然,糟糕的虚拟案例对用户正确理解模式误导作用,往往也是巨大的,甚至是毁灭性的。)