`
grunt1223
  • 浏览: 422921 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

设计模式初学者教程(上)

阅读更多

Good cooking takes time. If you are made to wait, it is to serve you better, and to please you.
美酒的酿造需要年头,美食的烹调需要时间;片刻等待,更多美味,更多享受。—— 《人月神话》


写在前面

前段时间,部门要求对新人进行设计模式方面的培训,因此就有了本文的雏形。当时由于时间原因,只做了ppt,讲完之后觉得意犹未尽,趁这几天项目完成,将讲稿完善后分享给各位。相关的ppt可在此处下载http://grunt1223.iteye.com/blog/549893


最后,祝开卷有益。

欢迎来到模式的世界
且慢,我们为什么需要模式?大学里开发“Hello World”我们用到了模式吗?在GOF的设计模式之前设计的程序难道都不是好的程序吗?

王尔德曾说,百折不挠的决心若与科学法则相抵触,犹如江心补漏劳而无益。若想翘起地球,必先懂得杠杆原理和使用规律。当然哲学上来说,一切规律都是相对静止而绝对变化的,因此那些较为稳定、变化不那么频繁的规律更是我们追求的目标,毕竟,大家都想要掌握一些一劳永逸的东西。

有兴趣的同学可以去看看《IT十大死对头》这篇文章,里面讲述了Linux单挑Windows以及Google对抗所有人等等,但传达给我们开发人员的精神只有一点,就是技术日新月异懂的程度堪比摩尔定律。尽管我们不提倡重新发明轮子,但是谁也不知道下一个轮子是谁发明的,并且不同“厂家”的轮子使用起来总会有一点差异;因此抛开具体的轮子不说,较为理想的是我们能够从宏观(设计模式角度)上掌握轮子的适用场景、改进它的使用方法等,或是能够从微观(算法角度)上了解轮子的具体结构、工作原理甚至DIY。

好了,你已经知道设计模式是一种相对稳定、适于宏观把握的规律,那么使用设计模式究竟有什么好处呢?有些文章将鼓吹设计模式无所不能,在我看来,设计模式主要有两个好处:

1、 以经验复用替代代码复用
一切皆可复用!技术复用范畴很广,由低到高分别包括设计复用、组件复用、类库复用、代码复用,而设计模式提供了的“经验复用”,则是最高层次的复用。GOF《设计模式》所做的就是总结了面向对象设计中最有价值的经验,并且用简洁且可复用的形式表达出来。

2、 共享词汇的威力

假设去肯德基吃东西,你可能会对负责点餐的服务员说:
给我一对用黑胡椒和新奥尔良秘制酱烤制的鸡翅,一个北京风味的、包含鸡肉、胡萝卜、黄瓜的特大春卷,一份夹了炸鸡腿、色拉酱和蔬菜的面包,一杯350ml的、加了冰块的百事可乐,另外把土豆打碎放在杯子里给我 @¥#%!¥
当然,服务员小姐很有礼貌的说:重复一遍,您要的是一对新奥尔良烤翅、一份老北京鸡肉卷、一个田园脆鸡堡、一杯中可乐还有一份土豆泥,对吗?

得到你的默许之后,她又会对后面负责送餐的小弟说:9527,这边来一对烤翅、一份老北、一个田园、一杯中可、一杯泥。


同样的意思源自于不同的表述,这就是人类语言的魅力;但现实中往往存在的是,哪怕是略微的表述不同也会造成别人极大的误解,那是沟通的陷阱。为了解决这个问题,各行各业产生了形形色色的“行话”,它们不仅将纷繁复杂的语言精炼化,还最大程度上避免了误解的产生。如果你认为肯德基点餐这个例子离软件设计太远的话,请看下面一个例子:

华仔:我建立了一个专门对付琛哥监视类。它能够联系所有的探员,而且任何时候只要韩琛有轻举妄动,它就会通知每个人。最棒的是,任何警员都可以随时加入或退出这套系统。这样的设计可谓相当的面向对象
伟仔:华仔,只要你说用了“观察者模式”,我就懂了

总得来说,共享词汇具有如下作用:

  1. 用更少的词汇作更充分的沟通
  2. 避免误解
  3. 在设计阶段,尽可能停留在设计层次,而排除编码阶段的影响 帮助初级开发人员迅速成长


既然你已经了解了共享词汇的威力,以后碰到“张口闭口模式”的人,千万不要武断地以为他是在炫耀或是显摆,有可能他在他的圈子里已经习惯于共享词汇了

策略模式         上兵伐谋,其次供交,其次伐兵,其下攻城。—— 孙子


在讲解策略模式的定义之前,请看下面的需求:

全聚德烤鸭集团需要一个可以展示他们可爱鸭子的平台,希望用户可以通过这个可视化的平台“亲眼”看到全聚德的鸭子游泳或是呱呱叫,以此来提升他们低迷的股价。他们最重要的需求就是这个平台要有较强的灵活性和扩展性,因为没有任何预算可以支付任何升级或重构的费用。
让我们先来看看第一个“面向对象”的设计方案(继承):



所有的鸭子都既会呱呱叫(quack),也会游泳(swim),所以这一部分的代码由超类(Duck)负责实现。但每一种鸭子的外观 都是不同的,所以Duck类中的Display方法是抽象的,将其具体实现延迟到子类(MallardDuck、RedheadDuck)中去。

正当一切看起来都很好的时候,新的需求来了。由于金融海啸的影响,全聚德公司的竞争压力骤增;在为期一周的头脑风暴会议之后,公司高层决定向潜在的客户展示一些令人印象深刻的元素,来提高公司低迷不振的股价,譬如说展示一些会飞的鸭子……

幸好当初我们采用了面向对象的设计,只需要在超类(Duck)中添加fly()方法,然后所有的鸭子(MallardDuck、RedheadDuck)都会继承它,这是展示OO强大继承威力的时候了……

但是,可怕的事情发生了,测试人员在你的系统中看到了一些在天空中飞行的橡皮鸭子(RubberDuck,陪伴鸭子玩耍的玩具),它们虽然是鸭子,挤压它可以发出怪叫(quack),也可以浮在水面上(swim),但是却不会飞翔(fly);并非所有的Duck子类都会飞。如果在基类中加上新的行为,将会迫使某些不适合该行为的子类,也具有该行为;对超类代码所作的局部修改,影响层面可不只是局部!

一个想当然的解决方法是,可以直接覆盖橡皮鸭类中的fly方法为空,神不知、鬼不觉,就好像一切都没有发生过一样;但这样做会有以下问题:
1、如果存在大量的不会飞行的鸭子子类,您将被迫多次覆盖fly方法为空,这样就失去了继承的意义了;即便你的功能可以正常实现,你是否闻到了代码的坏味道?每当有重复代码的时候,就应该考虑重构……
2、可是如果以后加入诱饵鸭(DecoyDuck),又会如何呢?诱饵鸭是木头假鸭,不会飞也不会叫。您从来都不是生物学分类专家,鬼才知道下一次需求方会不会再创造唐老鸭(不会飞、不会游泳、会叫)……

抽象超类继承可能不是答案,因为您可能不是业务领域专家,无法事先预料到需求会如何改变;按照上述方案,每当有新的鸭子具体实现类出现,开发人员就要被迫检查并可能需要覆盖fly()和quack()......这简直是无穷无尽的噩梦。

重新整理一下思路,我们想要的是一种更清晰的实现方案,让“某些”(而不是全部)鸭子的子类型可飞或可叫。究竟谁来决定它的行为呢?当然是具体实现类自己啦。请看下面的实现方案:



我们可以把fly()从超类中取出来,放进一个“Flyable”接口中,只有会飞的鸭子才实现此接口。同样的方式,也可以设计一个”Quackable”的接口,因为不是每一个鸭子都会叫。每当加入一种新的鸭子后,可以自行决定是否需要实现Flyable或Quackable,而不会去影响原有的代码,这是多么美好的事啊……

可是,该系统的维护团队可不这么想!他们认为,这真是一个超笨的主意,这么一来重复的代码会增多,如果你认为覆盖几个方法不算什么,那么对于48个Duck的子类都需要稍微修改一下飞行的行为,你认为如何?!

让我们来总结一下失败的教训:并非所有的子类都具有飞行(fly)和呱呱叫(quack)的行为,鸭子的行为在子类里会不断地改变,所以继承显然不是最适当的解决方案。虽然Flyable与Quackable接口可以解决“一部分”问题(即不会再有会飞的橡皮鸭),但Java接口不具有实现代码因此造成了代码的无法复用,从而失去了面向对象的意义。这意味着:无论何时你需要修改某个行为,你必须得往下追踪并在每一个定义此行为的类中修改它,一不小心,就会犯下大错。

莎士比亚说:不要指着月亮起誓,它是变化无常的,每个月都盈亏圆缺;你要是指着它起誓,也许你的爱情也像它一样的无常。的确,不变只是愿望,变化才是永恒。幸好,设计模式的宝库里有一个设计原则可以帮助我们去更好使用地适应它(而不是去对抗它):

引用
封装变化,找出应用中可能需要变化的部分,把它们独立出来,不要和那些固定不变的代码混合起来。



这句话的概念似乎很简单,但它几乎是每个设计模式背后的精神所在。基本上,所有的模式都提供了一套方法让“系统中的某部分改变不会影响其他部分” 。

我们知道Duck类中的fly()和quack()会随着鸭子的不同而改变。为了要把这两个行为从Duck类中分开,我们将把它们从Duck类中取出来,建立一组新类来改变每个行为。



依照上面的思路,如何封装飞行和呱呱叫的行为呢?如果我们将具体的fly和quack直接绑定给duck类的话,我们就丧失了动态改变fly和quack行为的能力,试想一下,如果某一只鸭子(的实例)翅膀受伤了,它的fly行为就会改变,这意味我们最好是使用弹性的设计原则,即:

引用
接口编程原则: 针对接口编程,而不是针对实现编程



现在,看一下我们对鸭子行为的封装:



使用上述的设计,可以让飞行和呱呱叫的动作被其他对象复用,因为现在它们是具有代码实现的类而非接口了,并且这些行为已经与鸭子类无关了;而且我们可以新增一些行为(比如喷气式飞行、骑着扫帚飞行等),不会影响到既有的行为类(FlyWithWings、FlyNoWay),也不会影响“使用”到已有行为的类(各种Duck的具体子类)。

好,让我们看看最终的成果:



恩,抽象超类(Duck)与之前的设计相比,多了以下的内容

  1. 以接口的形式封装了行为(FlyBehavior、QuackBehavior),并以成员变量的形式(flyBehavior、quackBehavior)保存在类中,同时提供相应的setter方法(setFlyBehavior、setQuackBehavior)以达到动态改变其具体实现的目的。
  2. 由超类来负责行为的调用。performQuack和performFly内部分别调用了quackBehavior.quack()和flyBeavior.fly()。这并不等于说子类对行为的调用没有控制权(否则子类的实现就完全一样了),子类可以通过setter方法(setFlyBehavior、setQuackBehavior)来绑定具体的行为类,从而控制具体的行为。


我们往往认为“面向对象 == 继承”,在我们的例子当中,鸭子的行为不是继承来的,而是和适当的行为对象“组合”来的,并且有一个原则可以支持这种做法:

 

引用
组合原则: 多用组合,少用继承。


恩,你可能奇怪,讲了这么多怎么还没有切入“设计模式的正题”,其实你已经学会了第一个设计模式:

 

引用

策略模式定义了一系列算法族,并加以封装,让它们之间可以互相 替换;此模式让算法的变化独立于使用算法的客户。
——  GOF


如果觉得鸭子的例子不够深刻的话,让我们来开发一款类似于Diablo的ARPG冒险游戏吧:



Blizzard邀请您参与最新款的动作冒险游戏《圆桌骑士》的设计工作。您将能利用开发商提供的代表游戏角色的类和代表武器的类,每名角色一次只能使用一种武器,但在游戏过程中可以切换、买卖、装备、舍弃武器;您可以提出比较好的架构方案吗?



一群“富有策略”的圆桌骑士看上去应该是这样的:



每个Character“有一个”WeaponBehavior,在冒险过程中切换、装备、买卖或是丢弃武器时,只要调用setWeapon方法即可。每一个具体的Character(King、Queen等) 都包含一个weapon的属性以及一个fight方法,后者在其内部调用WeaponBehavior接口的useWeapon方法,任何武器都可以实现WeaponBehavior接口,包括拳头、弹弓、口水,只要它实现了useWeapon方法。不同的武器的useWeapon可能千差万别,比如劈刺、挥砍、射击、喷吐等等。

现在,您应该对策略模式有所了解了吧,下面做一下小结:

策略模式的优点:

  • 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到超类里面,从而避免重复的代码。
  • 策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。
  • 使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。


策略模式的缺点:

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。
  • 策略模式造成很多的策略类

 

参考文献



http://www.jdon.com/

http://www.cnblogs.com/Terrylee/archive/2006/07/17/334911.html

http://sourcemaking.com/design_patterns

http://www.iteye.com/forums/tag/Design-Pattern

http://www.chinajavaworld.com/forum.jspa?forumID=44

http://www.ibm.com/developerworks/cn/java/design/#N100C1

http://www.blogjava.net/AllanZ/archive/2008/08/23/223890.html

 

  • 大小: 23.3 KB
  • 大小: 30.6 KB
  • 大小: 27.5 KB
  • 大小: 36.2 KB
  • 大小: 47.4 KB
  • 大小: 73.1 KB
  • 大小: 22.9 KB
  • 大小: 28.8 KB
  • 大小: 48.4 KB
  • 大小: 44 KB
  • 大小: 5.6 KB
  • 大小: 8.5 KB
  • 大小: 6.9 KB
  • 大小: 30.2 KB
  • 大小: 25.3 KB
  • 大小: 36.1 KB
  • 大小: 19.2 KB
  • 大小: 42.6 KB
  • 大小: 31 KB
  • 大小: 35.8 KB
  • 大小: 41.5 KB
  • 大小: 18.9 KB
  • 大小: 5.6 KB
  • 大小: 8.5 KB
  • 大小: 6.9 KB
23
3
分享到:
评论
9 楼 mqh444 2009-12-18  
不错,学习了。
8 楼 piratesean 2009-12-17  
谢谢,举得例子非常形象。学习了
7 楼 conservatism 2009-12-17  
这不就是head first吗
6 楼 avanry 2009-12-17  
写的很好,学了一个设计模式
楼主继续写啊,就等下了
5 楼 grunt1223 2009-12-16  
tianmo2008 写道
请问你的uml用什么工具画的?挺好看的,好操作吗?我用的几个工具画出来的图都很难看

visio
4 楼 tianmo2008 2009-12-16  
请问你的uml用什么工具画的?挺好看的,好操作吗?我用的几个工具画出来的图都很难看
3 楼 kingdu_12 2009-12-16  
学习了:)
2 楼 terryang 2009-12-16  
貌似在哪看到过类似的东东~~
1 楼 chencheng85 2009-12-16  
  
好好学习学习。。。

相关推荐

    java设计模式经典教程

    - **新手开发者**:对于初学者来说,学习设计模式可以帮助他们更快地掌握软件设计的基本原则和技术。 - **有经验的开发者**:对于经验丰富的开发者,学习设计模式可以进一步提升他们的技能,帮助他们更高效地解决...

    设计模式实训教程代码 刘伟 Java

    此外,刘伟老师的教程强调了每个任务的代码都已经完美运行,这意味着每个模式的实现都是经过验证的,这对于初学者来说是一份非常宝贵的资源,可以直接运行和调试代码,加深对模式的理解。 总结起来,这份《设计模式...

    Java版设计模式教程

    本教程“Java版设计模式教程”专注于使用Java语言实现这些设计模式,帮助开发者深入理解面向对象编程中的最佳实践。 首先,我们来了解一下什么是面向对象编程(OOP)。面向对象编程是一种编程范式,它将现实世界的...

    java设计模式高清教程

    总之,"java设计模式高清教程"提供了一份宝贵的资源,涵盖了Java设计模式的各个方面,无论是对初学者还是经验丰富的开发者,都有很大的学习价值。通过深入学习和实践,我们可以提高代码质量,增强系统设计能力,更好...

    uml与设计模式培训教程

    这份教程源于中软的培训课程,以其简洁易懂的特性,为初学者提供了宝贵的指导,有助于提升你在软件设计和开发领域的专业技能。 UML,全称Unified Modeling Language,是软件工程领域的一种标准化建模语言,用于可视...

    设计模式经典教程PDF

    这份"设计模式经典教程PDF"涵盖了23种经典的设计模式,对于任何希望提升编程技能和理解面向对象设计原则的程序员来说,都是不可或缺的学习资料。 首先,让我们逐一探讨这些设计模式。首先是单例模式,它保证一个类...

    Visual Studio NET初学者教程(精品)

    **Visual Studio .NET 初学者教程** Visual Studio .NET 是微软公司推出的一款强大的集成开发环境(IDE),专为构建基于.NET Framework的应用程序而设计。它提供了丰富的工具集,支持多种编程语言,如C#、VB.NET、...

    设计模式中文版教程PDF格式

    设计模式是软件工程中的一种重要概念,它代表了在特定情境下解决常见问题的最佳实践。这份《设计模式》中文版教程PDF格式的资料...无论是初学者还是有经验的开发者,都应该将设计模式作为持续学习和提升技能的一部分。

    GOF设计模式中英文+设计模式精解中英文

    总之,GOF设计模式和设计模式精解是软件工程师的宝贵参考资料,无论你是初学者还是经验丰富的开发者,都应该熟悉和掌握这些模式,以便在项目中灵活运用,提高代码质量和可维护性。通过深入学习这些设计模式,不仅...

    适合于初学者的关于Java的设计模式教程.zip

    本教程集合了多种设计模式,旨在帮助新手快速入门。 1. 单例模式(Singleton):保证一个类只有一个实例,并提供一个全局访问点。在Java中,通常通过私有构造函数和静态工厂方法实现。单例模式常用于配置中心、缓存...

    PHP5设计模式电子教程PDF全书

    《PHP5设计模式电子教程PDF全书》是一本深入探讨PHP编程中设计模式的宝贵资源。设计模式是软件工程中的重要概念,它们是为了解决常见...无论你是初学者还是经验丰富的开发者,这本书都将为你提供宝贵的洞见和实践指导。

    PS初学者教程

    本教程“PS初学者教程”旨在为初学者提供一个基础到进阶的学习路径,帮助他们快速掌握这款强大的工具。 在学习PS的过程中,首先会接触到的是界面布局和基本操作。PS界面包括菜单栏、工具箱、选项栏、面板等,每个...

    zemax初学者教程

    《ZEMAX初学者教程》一书为光学设计与分析领域的初学者提供了全面而深入的指导,涵盖了ZEMAX软件的基础知识与高级应用技巧。ZEMAX是一款由ZEMAX公司开发的专业光学镜头设计和光学系统分析软件,以其强大的功能和用户...

    设计模式C++教程

    设计模式是软件工程中的一种最佳实践,它是在特定上下文中解决常见问题的模板,为软件设计提供了一种标准的解决方案。...因此,无论你是初学者还是经验丰富的开发者,都应该抽时间学习并应用这些设计模式。

    C/C++设计模式基础教程

    ### C/C++设计模式基础教程知识点详述 #### 一、设计模式概述 设计模式是一种在软件工程领域中被广泛采用的概念,它旨在提供一种通用的解决方案,用于解决常见的编程问题。通过学习和掌握设计模式,程序员能够编写...

    PHP初学者入门教程

    5. **模板引擎与MVC模式**: 了解模板引擎(如Smarty)的使用,以及Model-View-Controller设计模式在Web开发中的应用。 ### 其他相关技术 1. **ASP与JSP**: ASP是微软的服务器端脚本技术,JSP是Java的Web开发框架,...

    设计模式教程及笔记

    设计模式是软件工程中的一种重要概念,它代表了在特定情境下解决常见问题的最佳实践。设计模式并不是具体的代码或库,而是一种通用解决方案的...无论是初学者还是经验丰富的开发者,这份教程都将是一份宝贵的参考资料。

    Java语言初学者教程集锦

    【Java语言初学者教程集锦】是一套专为新手设计的鸿蒙系统适用的Java学习资源,旨在帮助初学者掌握编程的基本概念和技能。在这个压缩包中,包含了两个主要文件:`java基础入门教程.rar` 和 `welcome.txt`。下面我们...

    PS教程初学者教程

    本教程“PS教程初学者教程”专为刚接触Photoshop的用户设计,旨在帮助他们掌握基本功能并能实际操作。 首先,我们要了解Photoshop的工作界面。在启动PS后,你会看到一个由多个面板组成的窗口,包括菜单栏、工具箱、...

    计算机设计模式_教程

    总的来说,“计算机设计模式_教程”是软件开发者的宝贵资源,无论你是初学者还是有经验的开发者,都能从中获益。通过深入学习和实践,你将能更好地应对各种复杂的软件设计挑战,提高软件开发的专业素养。

Global site tag (gtag.js) - Google Analytics