让编程融入生活

 http://www.taomartialarts.com/sch/sch_g_tao.gif

“人法地,地法天,天法道,道法自然。”——《道德经》

Tao is everywhere! 有人甚至想证明编程之道和生活之道是同一个

曾几何时,我把设计模式当成了程序设计之道,一本GOF秘笈啃了好几遍,好不容易看懂了一些,突然一群人跳出来说搞设计模式就是装B……不过我还是在写程序的时候留意着尝试使用,或者说是套用设计模式。但发现很少能使用到,一方面使用的高级语言已经包含了很多设计模式在里面(也就是说不知不觉已经在使用设计模式了),另一方面即使看懂了书实际当中很难想得起来使用。设计模式比较抽象,我发现只有在实际当中使用之后才能真正理解,比如一开始的Singleton模式是在最初实习的时候接触到的,后来在一个项目中,为了封装复杂的XML的组装,我尝试使用了Builder模式,分步骤去组装header和body各个部分,这才对这些模式有了深刻的印象。

当到了工作之后,用C语言了,觉得可以把设计模式丢掉了,但逐渐发现其实C语言写的程序也有类似面向对象的思想在里面,比如公司里面好多应用都用到的状态机,我觉得就是和状态模式相类似的。前些日子参加了一个设计模式的Training,估计是其他人不感兴趣,老板把机会给了我,我不参加白不参加,可以少干三天活呢!培训老师是北京的一个培训机构的,三天听下来感觉确实是对设计模式有深入的理解,他的某些例子确实是令我茅塞顿开呢。

下来之后逛书店的时候看到一本设计模式卡通书(这年代都不能静下心来看书了,要一些搞笑的元素才能打起精神),当小说简单看了一遍,虽然有些例子确实是有点牵强,但作者让编程融入生活”的精神令人敬佩!

培训老师说设计模式有三种境界,境界一:拔剑四顾心茫然;境界二:过犹不及也;境界三:无招胜有招。我现在还处于第一种境界,所以有必要总结下所有的设计模式,不求滚瓜熟烂,只要看到设计模式的名字能想起里面的机制。

首先是面向对象思想,三大思想应该是尽人皆知的了:封装,继承,多态。不要再去背那些“就业指导书”上的解释了,那些对你的记忆和理解一点用处都没有,当你被问到这样的问题的时候,我觉得你只要把你在实际写code的时候怎么用到这三大思想说一下就可以了。比如多态,多态就意味着我可以这样写code:

Parent son = new Child(); //Child继承Parent

另外,面向对象设计还有一些基本的原则:

  1. 单一职责原则(SRP)
    大鸟:手机的功能职责太多了,看到UFO用手机拍,手机摄像功能太差,拍不到。按照单一职责原则,手机就只应该负责打电话,DC负责摄影,DV负责摄像……(囧rz,要是手机只负责打电话,我就失业了)。
  2. 开放封闭原则(OCP)
    这条不用例子也很好理解,就是对扩展开放对修改封闭。大鸟:比如弹性工作制,每个工作日工作8小时,这是不能更改的(对修改封闭),但工作时间段每个人可以按照自己的情况安排,可以是9:00AM到5:00PM,也可以是8:30AM到4:30PM(对扩展开放)。
  3. Liskov替换原则(LSP)
    LSP是指子类型必须能替换掉它们的父类型。我们写code的时候尽量使用父类型(比如方法的参数),这样就能方便的变换子类型。大鸟:企鹅虽然是一种鸟,但不要把企鹅类继承自鸟类,因为企鹅不能飞,如果企鹅类继承了鸟类那么就会违背LSP,也就是说企鹅类不能替换掉鸟类。(解决方案是可以让会飞的鸟实现fly接口)
  4. 依赖倒置原则(DIP)
    大鸟:如果电脑主板也像收音机那样设计,主板直接依赖于内存,那么内存坏掉之后主板也要换掉。而现实生活中主板和内存都依赖于它们共同的抽象——插槽,这样就只要替换坏掉的内存即可。这就是DIP的一个方面,高层模块不直接依赖于低层模块,而是它们都依赖于它们的抽象(倒置)。而插槽是标准的,不会依赖于不通品牌的内存,这就是DIP的另一个方面,抽象不依赖细节,细节依赖于抽象。
  5. 接口隔离原则(ISP)
    不要把所有的功能都放在一个巨型接口中,应该按照功能或者角色分成不同的接口。例如【原】,如果移动只有一个套餐,包含了亲情号码,来电显示等所有的功能,那么如果只要开通来电显示,亲情号码也就不得不开通。
  6. 迪米特法则
    小菜第一天上班需要配置电脑,可是当他去找IT部门的工作人员时,那个人刚好请假了,导致小菜一天没做事。大鸟:如果小菜通过IT主管去和IT不能交互就不会出现这种情况了,IT主管可以叫另外一个员工来帮助小菜。迪米特法则是指如果两个类没有必要直接交互那么它们应该通过第三方来转发这个交互。
  7. 合成/聚合复用原则
    尽量使用合成/聚合而不是继承。聚合:大雁<-->雁群;合成:大雁<-->翅膀,头等等。

好,开始探讨设计模式之道与生活之道。老师说,设计模式的本质有两点,1,面向对象思想;2,封装变化。

首先是创建型模式。创建型模式要处理的是对象的创建,目的是封装创建的变化。常用的设计模式有Simple Factory,Factory Method,Abstract Factory,Builder,Singleton,除此之外还有Prototype。

  • Simple Factory
    这个模式很简单,就是把对象的创建过程封装起来。比如【原】要创建包子对象,我不会做包子,做包子要弄面粉啊,馅啊什么的,于是我就把创建包子的过程封装成一个静态函数,这个函数可以接收一个参数,根据这个参数可以创建不同的包子,菜包,肉包,三丁包……
  • Factory Method
    同时对某系列产品和它们的工厂进行抽象,客户端只需要和这个抽象打交道。还是【原】包子的例子吧,现在是做包子系列产品,我发明了几种机器,可以自动做包子,比如肉包机器可以自动做肉包,菜包机器可以自动做菜包……同时我又做了一个总的控制器,只要把总控制器接到包子机器上就能源源不断的给我做包子,比如把总控制器接到肉包机器上,我一按按钮就能自动开始做肉包,接到菜包机器上就做菜包。这里的包子机器就是工厂,包子机器上都有统一的接口也就是工厂方法,不同的工厂方法产生不同的包子。另外,总控制器是一个全局变量,做不同的包子的时候只要修改它这一个地方即可。
  • Abstract Factory
    当要产生一个产品族的时候可以用抽象工厂模式。【原】我们的包子铺生意兴隆,现在推出包子套餐,一个套餐包含三种包子,肉包,菜包,三丁包。套餐又分大中小三种,大套餐包含的都是大包子,中套餐则都是中包子,小套餐当然都是小包子。这时,如果用Factory Method复杂程度可想而知,而且如果要增加套餐类型就更是难上加难。现在,我们创造三个机器(工厂)分别能产生三种套餐,大套餐机器能依次产生大肉包,大菜包,大三丁包,并进行打包,中套餐机器,小套餐机器都是类似的。同样,有一个总控制器作为全局变量,做不同套餐时只要修改它即可。
  • Builder
    这个模式可以将Process和实际的生产相分离,这样同一个Process可以生产不通的产品。就像软件公司的同一个Process可以生产出嵌入式软件和企业软件等等。【原】各位是不是很好奇那个包子机器是怎么做的,我来透露下独家绝技吧。包子,有两大部分组成,包子皮,包子馅。这两个部分做起来都是相当麻烦的,没有食堂大娘的手艺那肯定会失败的。所以要把制作它们的过程封装起来,同时还需要一个控制器(Director),用来控制整个做包子的Process,比如先做包子皮,然后做包子馅,最后用包子皮把馅包起来。好,现在把做包子皮的模块和做包子馅的模块插入控制器,按下按钮,香喷喷的包子出炉咯。如果要做大包子,就把大包子的包子皮的模块和做包子馅的模块插入控制器,同样,要做小包子,只要把小包子的包子皮的模块和做包子馅的模块插入控制器。简单吧?
  • Singleton
    大鸟:产生某些类的对象也要计划生育,生还是不生是自己的责任。
  • Prototype
    这个模式不常用是因为在某些高级语言中已经实现了,比如Java当中的Object.clone()。其中还有一个Deep/Shallow Copy的问题请自行Google。

接下来是结构型模式。结构型模式关注对象之间组合的方式,封装对象结构(依赖关系)可能的变化。常用的结构型模式:Adaptor,Decorator,Proxy,Bridge。另外还有Composite,Façade和Flyweight。

  • Adaptor
    这个太容易举例了,变压器,转接头……大鸟举的例子是姚明当年要翻译来和教练队友交流。这个模式最适合“事后诸葛亮”,往往是当需要适配两个已经存在的模块的时候使用。
  • Decorator
    顾名思义,就是要装饰一个对象,使它能动态地增加功能。大鸟的例子是QQ秀穿衣服,想穿什么就穿什么。但这个例子似乎还不能体会到装饰器模式的精髓,学过Java的同志,想一下Java中的流,那就是典型的Decorator,每一层嵌套就是一次装饰。另外一个例子是比如有一个登录系统,有好几类用户,对A类用户需要对每次登录打log,B类用户每次登录需要鉴权,C类用户每次登录同时需要打log,还要鉴权。这个时候就可以创建两个装饰器,log装饰器和鉴权装饰器,对不同用户可以动态地装饰不同的装饰器以达到用户分类的目的。
  • Proxy
    这个模式也可以顾名思义,就是一个对象通过代理访问另一个对象,这和我们穿越GFW是一个道理。大鸟举的例子是隔壁班的男生通过名叫戴励的男生向娇娇送花献殷勤。同样的例子是《武林外传》中吕秀才通过小六向郭芙蓉求婚,结果可想而知……Proxy用处很多:1,远程代理;2,虚拟代理,代理开销很大的对象;3,安全代理,控制真实对象的权限;4,智能指引,在代理内部做一些内务,比如对真实对象的引用计数等等;
  • Bridge
    这个模式将将抽象和实现解耦,使它们可以独立的变化。最生动的例子是蜡笔与毛笔的故事
  • Composite
    这个模式其实在Decorator中已经使用到了,为了使装饰器同时也是被装饰者。Composite典型地用于树状结构的场合,比如一个Graphic,里面可以包含Text,Rectangle,Line等等,同时,一个Graphic里面可以有Picture(组合),这个Picture本身就是一个Graphic(继承),它里面也可以有Text,Rectangle,Line等等,当然这个Picture里面可能还有另外的Picture,就这样组成了一个树状结构,其中Text,Rectangle,Line等是叶子结点,而Picture是树枝结点。这个模式同时使用了组合和继承的优点。窗口组件系统也是同样的,例如在Java Swing中,一个JComponent中可以放置JButton,JLabel,JTable等组件,这些可以看作叶子结点,同时还能放入JPanel,JPanel中也能放置JButton,JLabel,JTable等组件,JPanel可以看作树枝结点。
  • Façade
    第一次见到Façade是在J2EE中,建议用Façade来访问EJB。大鸟炒股的时候遇上熊市,亏得一塌糊涂。小菜的同事“顾韵梅”更是在牛市里连连亏损,小菜不懂炒股,但也想在牛市里捞一把,大鸟建议买基金。基金“将投资者分散的资金集中起来,交由专业的经理人进行管理,投资于股票、债券、外汇等领域,而基金投资的收益归持有投资者所有,管理机构收取一定比例的托管管理费用”,可以看出基金就向一个Façade一样,不懂股票的人只要和基金交互就能变相炒股了。
  • Flyweight
    这个模式最经典的是用于文字处理系统中,在一篇文档中某个字符可能出现1w次,我们没有必要创建1w个对象,对于文字这种细粒度的对象我们可以对它进行共享。比如字符“A”,只需要创建一个对象,然后对它进行共享。字符“A”(内部状态)是不变的,但在文档的不同位置字符“A”的字体,颜色,大小(外部状态)可能是不一样的,这就需要靠外部的逻辑来控制。Java中的String就使用了Flyweight,hard code的String只创建了一个对象,是共享的。另外,大鸟的围棋的例子也很形象,只需要创建黑白棋子两个实例,棋子的位置是外部状态,可以在外部逻辑里面处理。

最后是行为模式。行为模式封装行为的变化,利用抽象特性达到整个构架的可扩展性。常用的行为模式有Strategy,Command,Template Method,Observer。另外还有Chain of Responsibility,Interpreter,Iterator,Mediator,Memento,State,Visitor。

  • Strategy
    实际生活当中策略无处不在,最常见的就是商场打折促销了,每次都是看到买多少送多少,还要想想怎么积分,甚至还有在某个时间段里面买满多少送多少积分,总之是乱七八糟啥都有。可以把这些促销手段看作是同一个任务的不同算法,由于它们是不断变化的,我们就要把它们封装起来。Strategy是我在写小程序的时候最容易想到的模式,因为我总是为以后更好的算法留一个后门。实现起来也很简单,对不同的算法进行抽象,并且提供一个上下文用来选择算法(方法有很多,最简单的可以用Simple Factory)。
  • Command
    【原】从大鸟的烤羊肉串例子延伸出去,考虑生活当中的点菜,服务员记下来的那张帐单就是封装的一个Command,我们是Command的发送者,厨师是Command的接收者,也就是发送者和接收者解耦,我们甚至不需要认识那个大厨。用了Command模式,我们甚至能对某些操作进行撤销/重做,比如一开始点了肉末胀蛋要换成银鱼胀蛋,只需要在帐单上改一下即可,当然前提是这个Command还没有从队列中取出,也就是说如果肉末胀蛋已经在作了,银鱼胀蛋就吃不成了。
  • Template Method
    【原】这个模式就是给人填表,抽象类设计好了模板,留下一些抽象函数给子类填写,从而完成整体工作。模板相当于算法的骨架,某些需要变化的步骤延迟到了子类。
  • Observer
    这是发生在小菜办公室的一件事情,老板有事出去了,于是大家看股票的看股票,看NBA的看NBA,它们已经和前台“童子喆”串通好了,如果老板回来就打个电话进来通知大家。谁知今天老板没有直接进去,而是叫“童子喆”帮忙打印东西,所以“童子喆”没机会打电话通知大家,等老板进办公室的时候“魏关姹”正兴奋地大叫“我涨停啦”!这里大家就是观察者,“童子喆”是通知者,观察者向通知者注册自己,一旦事件发生,通知者就通知所有的观察者。另外一个常见的例子是某人换手机号码之后群发短信:“我是XX,这是我的新号码……”通知所有已经注册的亲友同事。
  • Chain of Responsibility
    小菜想申请加薪,他向经理申请,可是经理只有批准2天假期的权力,经理需要把申请交给总监,但是总监只有权力批准1周之内的休假,所以总监把请求交给上一级总经理,总经理有权批准任意天数的休假,而且能批准500元以内的加薪。这就是一条职责链,请求被发送到职责链,如果某一级能处理就处理并返回,如果不能或不需要处理就把请求发送给下一级。写过Servlet Filter的同志对这个应该很有感触。
  • Interpreter
    一看到这个模式就想到大学时代写的PL0编译器,当然那个需要词法,语法分析,还要解释执行,Interpreter更简单写,只需要处理常见的表达式。一个表达式包含终结符表达式和非终结符表达式,一个非终结符表达式还能包含其他终结符表达式或非终结符表达式,很显然这可以形成一个Composite模式。大鸟的音乐解释器的例子很常见,索爱K700c上有一个音乐主持人程序,可以在上面加入鼓,萨克斯,电吉他,贝司从而形成一曲音乐。在音乐解释器的例子中,表达式仅包含终结符表达式,它们是音符,音阶,音速,客户端输入一段演奏文本,根据不同的指示符交给各个表达式处理(演奏)即可。
  • Iterator
    这也是被高级语言封装好的一个模式,用Java,C#的同志估计是天天要用到的吧。大鸟的例子很搞笑,公交车上的售票员对上车的人进行遍历迭代(买票),不管上来的人是男是女,是老是少,中国人还是外国人,甚至小偷也不例外。一句话就是提供了一个统一的遍历迭代接口。
  • Mediator
    大鸟的联合国的例子很形象,如果两个国家之间需要交互就在这两个国家之间画一条线,那么这么多国家之间就形成了一张错综复杂的网络。但如果有一个联合国,各个国家都通过联合国和其他国家交互,那就清晰多了。这很像以前网络书上的星型网络。
  • Memento
    这就是我们打游戏时候使用的“存盘大法”。当存盘的时候,把当前游戏状态存到一个对象中去(或写到文件系统里去),等到打老怪失败之后就可以恢复到存盘的进度。
  • State
    就是状态机。在做过的一个纯C语言的项目中就使用了状态机,面向过程的语言是这样实现状态机的:维护一个状态表数组,里面的每一项包含了状态类型,入口函数指针,出口函数指针,以及针对这个状态的事件处理表。而事件处理表的每一项包含了事件类型,事件代码,以及事件处理函数指针。另外有一个向操作系统或者平台注册的事件处理函数,这个函数接受底层的事件,并从当前状态的事件处理函数表里面找到对应的事件处理函数指针并且执行它。在事件执行的过程中有可能要改变当前的状态,由另外一个函数专门来做这件事情,只要输入需要转到的状态类型,这个函数会调用当前状态的出口函数,然后设置新的状态,并且调用新状态的入口函数。这样,一个状态机就转起来了。面向对象中的State模式原理是相似的,有很多状态类,另外加入一个Context类来记录当前状态,客户端只需要和Context类型交互,把事件或请求发送给Context,当前状态自动处理事件并且按照需要转到新的状态。这里举一个例子【原】也是项目中实际中的例子,就是网络连接的状态,首先程序的状态是init状态,当用户发起连接的时候,状态机收到连接事件,从init状态的事件处理表中找到处理连接事件的函数执行,此函数将状态机转到ppp connecting状态,表示底层已经开始连接,当ppp连接上之后,底层平台发送一个ppp connected事件,同样从ppp connecting状态的事件处理表中找到相应的函数处理,将状态机的状态置为ppp connected状态,接下来打开socket的过程都是相似的。
  • Visitor
    【原】我举一个顾名思义的例子,不同的人去visite几个景点做的事情可能是不同的。这里假定景点数目是固定不变的,比如就中山陵,夫子庙,珍珠泉吧,老年人去可能就是走走散散心,情侣去可能就是打情骂俏,外国友人去可能是买纪念品等等,各色各样的人(Visitor)过去可能做的事情都是不一样的。但这些景点不需要也不可能本身就具备这各色各样的动作,谁知道可能有一天某些人会去裸奔呢。这些动作都是归Visitor自身所有,只要Visitor来visite,景点只要Accept,并直接调用Visitor的动作即可。这样的好处很明显,可以扩展创建任意的Visitor而不改变原始的数据结构。当然前提是数据结构必须是稳定的。

好了,这就是所有的23个GOF设计模式,我相信生活中肯定还有其他的例子可以用来发现和设计模式相同之道。我这里要申明的是,这里的例子很多都是《大话设计模式》中的,另外也有培训老师举的例子,标注【原】的是我临时想到的。

本来写了一个PPT给Team做一个Workshop的,无奈公司重组,Team基本解散,只能留给自己做Reference了。唉,现在的处境感觉就和邵佳一在科特布斯那种顶级联赛的烂队一样,要命的是他还几乎没有机会上场施展自己的才华,也许选择离开对他来说是更好的选择(昨天看了德甲,有感而发,不是发牢骚……)。

我写此文的目的仅仅是想更加深刻的记忆这些设计模式,当遇到相似的问题的时候至少可以套用,因为自己可能还处于境界一(见上文)。光看这些文字可能还不形象,我觉得配合类图来看更好理解,很多书上,网上都有图的。

当然,对于一个程序员来说,设计模式仅仅是一方面,看看这位搞C++的老兄的知识结构图,当然不一定适合所有的人。

另外一方面,这里的模式不是所有的,比如在Java EE中,人们就总结了其他的好多设计模式。我觉得就像生活一样,随着时代的变迁生活模式也发生着翻天覆地的变化,有的模式可能已经不适用了,而一些新的模式正源源不断的产生出来。这种新陈代谢可能也是一种道吧。

说了这么多,泼盆冷水让自己清醒一下,总之,“道可道,非常道;名可名,非常名”,世间万物须待悟也……

[参考文献]

  1. 《大话设计模式》
  2. 《设计模式:可复用面向对象软件的基础》
  3.   很多优秀文章
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值