下午的昊海楼9层阁楼,阳光从分别从西侧落入,再从东侧大厦的玻璃幕墙反射回来,将阁楼填满了阳光。在这样的阳光下目睹一本新书的发布,有一种迷幻的味道。
(一)为什么要学设计模式?
如果说数学是思维的体操,那设计模式,就是面向对象编程思维的体操。
这里的设计模式,特指四人帮(GOF)于1995年出版的“Design Patterns-Elements of Reusable Object-Oriented Software”中所描述的面向对象的设计模式。当时面向过程的结构化编程正如日中天,面向对象方兴未艾,人们或许知道面向对象的三大特性:封装、继承、多态。但是在实践应用中,与面向过程相比就不免显得结构复杂,不够直观。此时,四人帮将程序员在面向对象编程过程中的最佳实践提炼成了23个设计模式,逐渐开始流行,然后成了程序员们的黑话,而面向对象方法对于程序维护、需求更改、功能扩展的良好适应性也逐渐影响整个行业。
说到这里,上面的问题就有了答案,学习了OO设计模式,并且在编程中养成了模式思维,才算是真正领悟了面向对象编程的妙用。
结合面向对象的六大原则,OO设计模式描述了类的抽象、剥离与组合,类集的典型结构与行为。但是模式不能孤立存在,对于每一个的功能类,向下都需要底层的数据结构与算法作为支撑来实现。而模式与模式的组合,向上则构成了软件的架构模式。
(二)OOP设计模式
GOF总结了23种设计模式,虽然现在的OO设计模式远不止这23种,但一开始的学习仍应从这里开始。具体的参考书目见文末,下面上图:
图中每一种设计模式都值得细读,不过篇幅所限下面简要介绍OOTV大赛五强选手。
(1)工厂方法模式
先聊一聊工厂方法三姐妹,她们包括:简单工厂,工厂方法,抽象工厂。他们都是使用户在不需要知道具体产品细节的前提下,保质保量的完成产品实例化的方法。比较而言,简单工厂,在工厂内部设置若干条件分支,使客户按需订制,优点是简单无需产生大量的类,缺点是不符合开闭原则,一旦有变更,就需要修改方法内部条件分支。工厂方法,将产品实例化延迟到她的子类,应对变更时只需要对工厂子类进行扩展。如果说工厂方法只是对产品进行简单的加工生产,抽象工厂,则可以满足更加复杂的装配生产。下面回到工厂方法。
情景:客户需要从若干产品中得到自己需要的产品,又不想知道产品的构造细节时。
问题:客户直接调用产品的构造器会使两者形成强耦合,不利于应对变更。
解决方案:使用工厂方法进行隔离。使得变更产生时,无需修改高层用户类。
UML图:见书,略
实际应用:商户需要订购1000台iphone,500台ipad,不需要自己找来图纸建设工厂,只需要向工厂发送订单即可。这样不论iphone如何升级换代,商户与工厂的接口不会改变,工厂包装了产品细节。
(2)适配器模式
情景:当开发软件需要使用第三方插件,但是接口与现有体系不同时;当软件升级换代重构后,必须使用原有组件,但是接口与新体系不同时。当需要调用的第三方软件(如DB)预计可能发生变化时。
问题:两个接口不同的组件,难以直接调用,或者预计会发生变更。
解决方案:使用适配器进行过渡。
UML图:见书,略
实际应用:你的安卓手机在路上快没电了,你忘了带充电器,而此时你朋友带着苹果充电器。嗯,这时你只需要一个适配器。
(3)外观模式
情景:软件开发中需要调用一个有多个具体功能模块的子系统时。
问题:直接地调用子系统中多个功能模块,会产生强耦合。
解决方案:给子系统添加一个facade接口,所有对子系统的调用都通过他来实现。
UML图:见书,略
实际应用:好的模式:去银行不管办什么业务都只需要找到一个柜员。反模式:去医院看病,你需要在挂号,分诊,看病,缴费,检查,取药每一个环节面对不同的人。
(4)观察者模式
情景:当一个事件发生,需要通知若干个对象做出相应反应时。
问题:事件源可能并不具备通知功能。
解决方案:设置一个观察者,当事件源发生时,通知注册会员(若干注册的监听者)做出回应。
UML图:见书,略
实际应用:一伙賊做坏事时,总会需要一个人在外围放哨。
(5)策略模式
情景:当一个类中部分功能稳定,部分功能有几种不同变化时。
问题:变化会给类的使用带来灾难性后果。
解决方案:将变化从类中剥离出来,单独封装,并提供接口调用。
UML图:见书,略
实际应用:哄老婆三板斧:甜言蜜语,送礼物,拿钱砸。嗯,还可以扩展。
(三)复合模式
日常软件开发可能需要用到若干种设计模式组合使用的情况,这在《Head First设计模式》中进行过案例探讨。
几种模式的使用若仅适用于当前软件,则仅能称之为普通的模式组合。若有一种组合能够抽象出来具有普适性,可应用于多种场合,则称之为复合模式。而MVC模式也被该书称之为复合模式之王。当然,先有MVC模式,还是先有GoF模式,我没时间考证,姑且认为MVC模式就是从GoF模式进化而来。
MVC模式中的单体模式:
(1)Controller:策略模式。当客户端发送一个请求,可以认为用户调用了Controller的一种策略,每次调用产生一个行为,成为事件源。
(2)Model:观察者模式。在这里,Model中的不同模块被定义为不同的观察者,用于观察Controller中特定策略产生的事件源,并通知给注册的监听者View。
(3)View:组合模式。被通知的View,将所需的子节点与叶节点进行组合,响应给客户端。
按照该MVC模式来审视我之前CEv5.0项目的MVC框架,确实并不符合面向接口和封装变化的精神,对其再次重构,则有以上框架图。
说说个人的感觉,理想中的MVC模式适合于桌面软件开发,对于Web应用,对于Servlet/JSP有这么几点不同:
(1)严格符合MVC模式的只有Controller模块,当浏览器发送Request时,可以有get、post、put、head、delete等8种策略(method)可以选择。
(2)View模块。JSP不是面向对象的,所以不能简单套用组合模式,不过Html文件的文档结构,的确是符合子节点和叶节点组成模式的。
(3)Model模块。在观察者模式中,观察者是通过调用监听者的响应方法来通知监听者的。在这里,由于JSP不是对象,不得不做一些调整。可以通过在Servlet中添加Dispatch方法作为Model的回调函数,来通知JSP对浏览器做出响应。
(四)设计模式的实际应用
(1)用模式审视自己的设计
这里本来想写,用模式思维来设计软件,后来觉得不妥,这样容易直奔过度使用模式而去。
关于过度使用设计模式是另一个话题,这里我觉得比较正确使用模式的姿势是,遵照OO设计六大原则去设计自己的程序框架,然后找出其中可能发生变更的地方,再用模式思维去审视自己的设计,并做出改进,然后再去实现细节。
当然,模式就像一套剑谱,真正的高手是无招胜有招,胜人者剑意而非剑形。我等小菜还是心怀理想,然后老老实实照着剑谱去练吧。
(2)过度使用设计模式的问题
学习模式也有一个坏处。学会了模式,就像有了一把锤子,看什么都像钉子,想去敲一敲。后果是什么?就是造成程序要做的事本来很简单,结果实现方式却过度复杂。
不过书中的建议是,初学者过度使用也无妨,哪怕用错了地方也无妨,这样反而可以使用中快速精进。等到真正熟悉了大部分模式的用法后,再去判断何时何地当用何种模式。
(3)反模式的用处
什么是反模式?通俗一点讲,就是关于模式使用的错题集,尤其是当用模式而不用,当变化来临时,造成十分被动的典型情况。
(4)更多的模式
除了1995年出现的GoF23种模式,二十多年间,不少工程师还把自己在工作中反复遇到的问题提炼成了模式,并且在网上与人们开源共享。主要包括两类:一是面向对象编程OOP设计模式的进一步扩展模式;二是用在其他地方的譬如架构模式、应用模式、业务流程模式、组织模式、用户界面设计模式、网页设计模式、领域特定模式等,可以作为进一步学习升级的方向。knowmore1knowmore2
参考书目
《设计模式:可复用面向对象软件的基础》
《大话设计模式》
《Head First设计模式》