对于设计模式,真的有很多话要说。由于随着不断学习,见解也会不断改变,因此我会不断修正这篇文章的内容。
如果你像我一样,很浮躁,也喜欢拿来主义,只想花最少的时间看到最核心的内容,请直接跳到第二节——重构“对设计模式的学习”
如果你喜欢我的文章,有耐心,那么请先看第一章。虽然很多个人的观点,但就像《重构与模式》中有一句话说的含义,本书的真正价值在于理解产生这些内容的思考过程。
重构“对设计模式的认识”
缘起
不知为何,我一直都对“设计模式”很抗拒。可能是,一开始不懂,然后又思想守旧不想学,初看《设计模式》的时候,觉得书写的稍微有些复杂,没心思看;之后一直没学也没觉得有什么问题,再后来看到一些文章吐槽一个hello world程序被设计模式折腾后变成一大坨代码(简单说就是过度设计),于是就没再想过去学了。再后来被新同事劳模写的代码所震撼,我分不清这些到底是因为学习了设计模式,还是因为学习了《effective java》,后来苏总又批评我写的代码差,脆弱的自尊心受到打击,于是迁怒到“设计模式”本身,总想推翻“设计模式”的光环。
偶然一个机会,当我问heqc有没有学设计模式,他说没有,然后随口说了句“重构啊”,他也不解释,我也不以为意。再后来,看到一些文章,极力推荐《设计模式》、《重构》、《重构与模式》,怀着对设计模式的敌意和一探究竟的心情,直接选了《重构与模式》来看,之后可以用一见钟情来形容。本着对《重构与模式》里面每一句话的深深认同,当它写到“如果不研究模式,你就是主动地将许多重要且优美的设计思想拒于千里之外”时,我无言以对。
同时,脆弱的自尊心迫使我另辟蹊径,选择了《Thinking in java》里面多次提到的作者另一本没出版的书《Thinking in Pattern》进行参考,了解大概。相比较而言,《设计模式》是不是太理论了不够接地气?《head first 设计模式》是不是又太罗嗦了有作秀之嫌?罗列一大堆“现象”反而让人看不清“本质”。而对于新手来说,我只希望能尽快用起来,让我多年的码农经验能够对标上这些术语。
我选择这个博客的标题,也是出于我对“重构”一词的热爱,希望对“初学者应该如何学习设计模式”做一次重构。
被人言过其实
请允许我以吐槽的口吻写,不针对DP本身,只针对那些痴迷DP的人,那些理论家,那些拿DP显摆的人。学习设计模式的必要性,远不像我看到的那些遇到解决不了的问题就捧着《设计模式》像捧着葵花宝典一样的人给我的错觉那样,就好像他们学了之后能把本来做不到的事情做到似的。
按《重构与模式》说的,《设计模式》写的时候java还没发展到现在这么高级,所以当时主要写给c语言的人看的?这么多个模式,只有几个是我觉得确实是需要技巧的,能令我眼前一亮,其余大部分都是能无师自通的。
反而是《重构与模式》一直强调的三点:去除重复代码、简化逻辑、说明意图,才是编码设计的核心。我们可以实现某个设计模式,或者仅仅趋向那个设计模式,甚至可以去除不使用模式,衡量做法对错的唯一标准,或者目的,始终应该仅仅是为了去除重复的代码、简化代码逻辑、说明代码处理的意图。
这也说明了为什么设计模式会有那么多争议,为什么有个大厂的程序员得到最优秀代码编写奖而他居然说自己不怎么会设计模式,但是会组织好每一句代码并写明白其用途。
为什么要学
我简单归纳设计模式的作用:
1、为了看起来很专业。让不懂得设计模式的人觉得你很牛。
2、为了与看起来很专业的人交流。或者说就是懂得“行话”,就比如股神说平仓、做空这些术语时,外行人听了后只会继续云里雾里和更加膜拜。
3、为了毕业文凭。好比正规军与游击队,没上过课的游击队也很有可能打败正规军,但正规军始终是有理论基础的职业选手。设计模式就是理论,多年码农的经验就是实践。
4、为了能优雅的顺手拈来。实践上升到理论,形成套路,以后再遇到类似问题,就可以不假思索,甚至举一反三。这其实就是设计模式最初提出来时的定义:“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次的使用该方案而不必做重复劳动。”。因此,设计模式就是对某些经常出现的问题的通用解决方案的抽象,它就是编程的套路。
重构“对设计模式的学习”
以下就是我对各个模式的说明,我觉得,也希望,一个表格足矣。
干货
设计模式 | 场景理解(面向对象) | 本质(一句话) | 详解 |
State 状态 | 青蛙王子 | 多态 | 其本质就是多态,强调的是面向接口编程过程中,同一接口(生物)下,不同的实现类(王子或青蛙)之间可以随意转换(王子变青蛙,青蛙变王子) |
Strategy 策略 | 问:你有什么应对策略?答:兵来将挡,水来土掩 | 面对不同情况(上下文)选择(条件)不同应对策略(多态对象) | 本质是多态,强调的是把选择不同的算法(单一行为)变为选择不同的多态对象 |
Visitor 访问者 | 青蛙王子接受不同题材的记者访问,各个记者都是见人说人话,见鬼说鬼话 | 两个多态类互相作为对方的参数 | 接口A的方法a接受接口B,并在a内以自己this作为参数调用B的方法b,b支持针对A的多态子类进行重载,从而实现A子类个数XB子类个数的排列组合处理 |
Builder 生成器 | 青蛙/王子 说学逗唱 讲一段相声 | 多态对象用各自的方法按同一个剧本完成同一件事 | 生成器模式属于创建型模式,但创建并不一定限于创建对象,还可以是创建(完成)一件事情,那么就是给定一个相声的剧本,只能用说相声的四种方式说学逗唱,让王子与青蛙各自表演一次。所以其本质还是多态,强调的是在处理过程中(多种行为),把不同表现结果的选择变为选择不同的多态对象 |
Template Mathod 模板方法 | 请假条模板 | 类继承 | 其本质就是类继承,强调的是像请教条模板一样,让父类实现不变的模板内容,让子类实现可变的请假原因及时间等 |
Creation Method 创建方法 | – | 特指创建对象的方法 | 《重构与模式》中为了澄清Factory Method而增加的模式,强调的是方法 |
Factory 工厂 | 生产产品的工厂 | 专门用于创建对象的类 | 强调的是类 |
Factory Method 工厂方法 | – | 模板类下的多态的创建对象方法 | 非工厂类下的创建对象的方法,强调的是模板模式下,子类特有的方法是用于创建对象 |
Abstract Factory 抽象工厂 | – | 多态工厂创建多态实例 | 通过替换同一接口A下的工厂实例达到替换其生成的同一接口B的不同产品实例,强调的是在工厂+多态 |
Command 命令 | 命令,不是告诉你什么,而是告诉你做什么 | 回调,传递“方法”而不是“参数” | 作为参数的对象不包含数据,只包含行为,让被调用方回调 |
Observer 观察者 | 观察关注点,订阅消息 | 订阅变化,等待被回调通知 | 被观察者维护一个观察者的列表(通过注册登记进来),当有变化时则逐个通知 |
Facade 门面 | 前台接待员可以作为一个公司的门面 | 封装多个类以进行简化 | 提供简单的统一入口,封装各种复杂的实现 |
Adapter 适配器 | 电源适配器 | 封装单个类以便能被面向接口编程时调用 | 封装不可修改的类去适配另一个接口,使其能以该接口的形式被调用 |
Proxy 代理 | 代理人,秘书代理经理处理事务 | 封装单个类以使面向接口编程调用时进行修改 | 对某一个类的调用,变为对另一个实现相同接口的类的调用,使其能做一些附加功能 |
Composed Method 组合方法 | 组合多个小方法以实现一个大方法 | 分而治之,自顶向下 | 提炼函数,形成树状方法调用的层次结构 |
Composite 组成 | 团队精神 | 遍历树或链表等数据结构的所有节点 | 整体(团队)由局部(组员)以一定的结构(团队组织形式)组成(树或链),运行整体的方法(团队行为),所有局部(组员)都要参与。通过遍历树或链表等数据结构,调用所有节点的方法 |
Chain of Responsibility 职责链 | 职能部门踢皮球,一个传一个 | 遍历链表直到找到正确的节点 | 实现相同接口的对象递归包含组成一个链,逐个依次被调用,直到应该进行实际处理的节点(职责所在)被调用为止 |
Decorator 装饰 | 化妆+美颜,说你漂亮一个装饰功能都不能缺 | 遍历可动态增加节点的链表的所有节点 | 共同接口的对象通过不断嵌套(称为装饰,实质为封装)形成一个职责链,强调可动态增加功能(装饰) |
FlyWeight 享元 | 英文直译:减少重量(对象的数量) | 把对象内的数据存放在外部 | 把对象内的基本类型数据抽取到外部存入数组中,不需要增加对象数量即可保存多个对象的内容。 |
Singleton 单例 | 单一一个实例 | 有且只有一个静态实例 | 属于编程技巧 |
Bridge 桥接 | 桥 | 面向接口编程为两端的不同实现搭起了一个桥梁 | 删除log4j1(实现型的jar),更换为log4j-over-slf4j桥梁(适配slf4j到log4j1的接口作为log4j1新的实现),slf4j作为门面,log4j-slf4j-impl(适配log4j2到slf4j的接口的实现) |
总结
看完上面的表格,我觉得对于java来说,设计模式大部分就是围绕:类、继承、多态、重载、面向接口编程、封装、抽象泛化这些基本的面向对象理念的使用方法所做的经验总结而已。因此我才说,其实没什么眼前一亮的东西,但确实是经验总结出来的宝贵设计原则,使用的好,能令代码变得优雅。
也就能明白了,为什么有些设计模式平常提的那么少,说的最多的好象一直是工厂模式,好像你就只懂工厂模式。其实是因为大部分大家都一直在用,不是你不会,而是根本没什么好值得说的。
当然也有例外,像访问者模式、用职责链代替ifelse等一些“技巧”,如果不说可能确实不会一下子想得到,但是,像这些技巧到底有多重要是值得讨论的,反正很多人不会也就这么过来了,会了也很少想用的。
每个模式除了本质之外,很多细节都可以变化,排列组合就千变万化了,甚至有些使用场景的不同就能导致从一个模式变为另一个模式。所以记住这些细枝末节根本就是浪费精力和时间,理解核心思想,你也能做到千变万化,根本不需要别人代劳,甚至被迷惑误导。
如果继续扩展延伸,有人也会把很多编程技巧进一步提升为“模式”,比如懒加载、空对象等等,那可更加说不清道不完了。
因此我更坚信,归根结底,还是《重构与模式》一直强调的三点:去除重复代码、简化逻辑、说明意图,才是编码设计的核心。如果必须补充的话,那就是灵活性、解耦