一、 设计模式的隐喻
武功套路是习武的门径。新手要一招一式地练习套路,烂熟于心之后,熟能生巧,在实战之中即可见招拆招、运用自如——此时习武之人已从“新手”成长为“好手”。“高手”则没有套路,实战之中只有自然反应,然而一招一式浑然天成、恰到好处,似有似无、无中生有。“高手”之上还有“高高手”,他们达到的境界非我等凭借金氏武侠小说可以揣测。
设计模式之于设计,好比套路之于武术。“新手”要一个接一个地学习模式,“好手”能够活用模式,“高手”则没有模式。
设计模式的“内功”是面向对象的基本原则。这些原则是“神”,模式是“形”。高手拼的是“内功”,对面向对象基本原则有了深刻的领悟,才能用好设计模式,避免“走火入魔”。
一般在设计模式著作的前几章都会介绍面向对象的基本原则,这几章非常重要。学通了这几章,后面的模式就不过如此了。学完了设计模式,也最好翻过头来重新看看这几章,保证会有新的领悟。
二、 为什么使用设计模式
对任何设计都可以凭主观(对设计很难做出客观评价)判断得出它是一个好的设计,还是一个坏的设计。使用设计模式是为了避免坏的设计。Martin叔叔在他的著作《敏捷软件开发 原则、模式与实践》中描述了拙劣设计的症状:
僵化性(Rigidity):设计难以改变。
脆弱性(Fragility):设计易于遭到破坏。
牢固性(Immobility):设计难以重用。
粘滞性(Viscosity):难以做正确的事情。
不必要的复杂性(Needless Complexity):过分设计。
不必要的重复(Needless Repetition):过多的重复。
晦涩性(Opacity):混乱的表达。
三、 什么时候使用设计模式
Martin叔叔的书中有段话:
在学习它们(设计原则和模式)的时候,请记住,敏捷开发人员不会对一个庞大的预先设计应用那些原则和模式。相反,这些原则和模式被应用在一次次的迭代中,力图使代码以及代码所表达的设计保持干净。
在这段容易被读者忽略的文字中,我体会到这样几层含义:
代码是设计(这是Martin叔叔强调的一个观点,这个观点可以参考《敏捷软件开发 原则、模式与实践》一书的附录D);
设计模式是为了使设计适应变化;
设计模式是重构的工具;
设计一开始就要保持干净、简单,以后仍然要保持干净、简单;
不能过度使用设计模式。
使用设计模式的目的是为了适应未来的变化,变化之所以存在是因为它的不可预知性——如果可以预知,则不能称其为变化。如何判断哪些需求可能变化,哪些需求可能不变,并且在最大程度上保持设计的干净、简单,这是些工艺问题,而不是工程问题。既然是工艺问题,那么就只能给出原则,不能给出标准。使用设计模式的大体原则可能是:对未来极有可能发生变化的问题给出最简单、修改成本最低的解。
四、 避免过度使用设计模式
易维护的程序首先要易理解,这一点远甚于其他。在易理解的代码上才好维护。过分地使用设计模式会增加程序的复杂性和晦涩性,让程序不易理解,从而降低了程序的易维护性。
Switch语句曾经遭致诟病,许多重构的例子就是拿Switch开刀。我认为Switch语句是高效的语句,可以写出极优雅、简单的代码。在很多情况下,直接使用Switch语句比把它拆成若干个Class更“干净”。
再比如,有一段四百多行的代码负责整个系统的调度,如果未来的变化仅仅是修改这四百行代码而不会大量添加代码,那么把这四百多行代码集中在一个函数里面,比将它拆分成十来个Class更加容易维护。
五、 讨论几个具体的模式
1、 创建模式(Creational Pattern)
工厂(Factory Method)模式是常用的模式。工厂模式的应用情景明确,设计思想简单。从使用多态到只用一个静态方法,工厂模式的变化形式有很多。我习惯简单地使用工厂模式,也就是使用只有静态方法的工厂模式。下面的工厂模式代码简单、干净:
MyFactory.GetClassInstance().DoFunction();
类厂并不承载业务逻辑,需求变化对类厂的影响通常很小。因此使用重量级的工厂模式往往并不划算。一组包含层次关系的重量级的工厂类,可能意味着过度设计。
单例(Singleton)模式和工厂模式关系密切。从实现的角度讲,单例模式是工厂模式的一个特例,但是两个模式的应用情景不同,因此它们属于不同的模式。
抽象工厂(Abstract Factory)模式是工厂模式的推广。抽象工厂模式的应用情景更加特殊和严格。在一个使用抽象工厂的设计中,如果未来发生不同产品族各自演化的情形,那么抽象工厂模式就可能崩溃了。在实际应用中,不同产品族各自演化,最终分道扬镳的情形是有的,用户提出这样的需求的确让人“触目惊心”。在使用抽象工厂模式之前,一定要保证从现在到未来都能够用一致的方式使用这些产品族。
将工厂模式稍加变化可以得到建造(Builder)模式。工厂模式的“加工工艺”是隐藏的,而建造模式的“加工工艺”是暴露的。这点不同,使建造模式在更加灵活的同时也有失优雅。
2、 模板模式(Template Method)和策略(Strategy)模式
模板模式和策略模式的应用情景类似,但实现方式不同,前者使用继承,后者使用委托。
模板模式有可能是最“古老”的模式之一,在使用面向对象技术的早期,“继承”大行其道,很多设计人员可能不自觉地使用过模板模式。模板模式的缺点是把具体实现和通用算法紧密地耦合起来,使得具体实现只能被一个通用算法操纵。然而在继承关系中,父类的信息可以更多地暴露给子类,这种(违背面向对象设计原则的)微妙的沟通在一些特定应用中显得更加灵活和方便。
策略模式是委托的经典用法。策略模式消除了通用算法和具体实现的耦合,使得具体实现可以被多个通用算法操纵。策略模式也增加了类层次,比模板模式复杂。
模板模式和策略模式通常可以互相替换。它们都像试卷,模板模式是填空题,策略模式是选择题。
3、 简化问题的模式
门面(Facade)模式把一组复杂的接口隐藏在一个简单且特定的接口后面。
调停者(Mediator)模式把对象之间的引用关系包装在一个特定的容器里面。
组合(Composite)模式描述了整体与部分的结构关系,并且允许用一致的方式处理这个结构。
上面几个模式对使用者而言,都在一定程度上起到了简化问题的作用。
4、 扩展功能的模式
访问者(Visitor)模式和装饰(Decorator)模式都可以在不改变现有类结构的基础上,动态地增加功能。
访问者模式把现有类结构上的对象“分配”到一个名为访问者的类中,在访问者的相应方法中配置对象、改变对象或扩展功能。
装饰模式把现有类结构上的对象“注入”一个装饰类中,在装饰类中扩展它的功能。
访问者模式和装饰模式在实际效果上是不同的。访问者模式可以把对象分配到相应的方法里,从而对每个对象分别进行加工或扩展。而装饰模式只能用一致的方式对所有的被装饰对象进行加工或扩展,要想实现不同的加工或扩展,只能增加新的装饰类。
过多的“装饰类”有可能使业务逻辑分散,并且使程序结构复杂。针对每一个具体的派生类,“访问类”都要有一个对应的方法,增加派生类的时候也要增加访问类的方法。扩展功能的需求是经常发生的,是否有必要使用上述模式则值得再三考虑。
5、 其他常用的模式
桥梁(Bridge)模式。Class是封装了行为和属性的容器,然而Class的一组行为可能独立演化,这时最直接的想法是使用继承,把各不相同的行为封装在不同的子类里。桥梁模式从另外的角度解决了这个问题。桥梁模式把独立演化的行为封装在另外一个类体系里,与原来的类体系分别独立演化,两个类体系在抽象层次是“使用”关系。在很多OO教材里面用Shape类封装属性和Draw方法,在桥梁模式里,“形状”和“画笔”是两组独立演化的类体系,在抽象层次,“形状”使用“画笔”绘制自己。
适配器(Adapter)模式是常用模式,它比较简单,有时和其他的模式配合使用。
命令(Command)模式被Martin称为“最简单、最优雅的模式之一”。命令模式的魅力在于它为每个类“培训”出了相同的技能,经过“培训”的类“柔性”更强,能够产生不可思议的能力。
6、 不太需要的模式
观察者(Observer)模式。Java和C# 都实现了观察者模式。
迭代子(Iterator)模式。在Java和C#语言里,可以用聚集类代替。
备忘录(Memento)模式。可以用Class的序列化能力代替。
责任链(Chain of Responsibility)模式。可以用其他的方式替代,例如观察者模式、语言本身提供的消息机制等。
解释器(Interpreter)模式。个人认为,它是个算法,不是模式。
代理(Proxy)模式。如果在一开始就知道某些底层策略一定会被替换掉,那么使用代理来隔离这些策略还是有必要的。否则,几乎没有使用的必要。
参考文献:
Robert C. Martin,Agile Software Development Principles,Patterns,and Practices.(中译本:《敏捷软件开发 原则、模式与实践》,邓辉译,清华大学出版社,2003年)
作者:
张昱 曾就职于浪潮集团、联想集团,现就职于中科院电子所。超过十年的软件工作经验。对分析设计、项目管理、编程实践有着浓厚的兴趣。
Martin叔叔没有给出“好”设计的定义。避免了上述症状的设计可能是个符合要求的设计,但未必是“好”设计。软件设计具有“艺术”特征,一个好的设计必定妥当、优雅、满足需求,妥当、优雅、满足需求的设计则未必一定是好的设计。设计模式的使用是为了消除软件设计的恶劣症状,而不是保证给出一个好的、正确的设计。设计模式可能源于人们对软件的恐惧感吧(林星的文章中有句话:方法论源于恐惧)。
Martin叔叔在接下来的一章(第七章、什么是敏捷设计)中对上述拙劣设计的症状进行了详细描述。我们看到,产生上述恶劣症状的根本原因都是需求变化。使用设计模式是为了消除设计的恶劣症状,而产生恶劣症状的根本原因是需求变化。那么是否可以这样说:使用设计模式是为了拥抱变化。
分享到:
相关推荐
### 活学活用:生活中的设计模式 本文旨在通过生动有趣的语言,将复杂的软件设计模式变得易于理解。文章围绕几种常见的设计模式展开,并通过实际案例解释了这些设计模式的应用场景及其背后的逻辑。 #### 1. 工厂...
翻转课堂的模式对传统教学是一种挑战,它要求教师转变教学理念,提升教学设计能力,同时也要求学生具备较强的自主学习能力和自我管理能力。在实际操作中,教师和学生都需要时间去适应这种新的教学模式,但一旦适应,...
在具体的架构设计实践中,同城双活架构是一种确保业务连续性的高级架构模式,它意味着在一个城市内建立两个活动站点,通常配备有相同的服务和资源。这样,如果一个站点出现故障,另一个站点可以立即接管流量和业务...
然而,值得注意的是,由于安全性和性能原因,现代浏览器已逐渐弃用了Java Applet技术,转而支持如HTML5、CSS3和JavaScript等更加安全和高效的技术栈。因此,在学习这些经典案例的同时,也应该关注最新的Web开发趋势...
### 城商银行双活容灾建设方案设计三大难点 #### 一、同城双活架构与容灾架构改造方面 ##### Q1: 针对存储级复制实现主备中心数据同步的架构,如何改造成双活模式? 在改造过程中,首先需要明确“双活”是指何种...
本文将从集团制造企业数字化转型顶层业务设计方案的角度,讨论制造企业数字化转型的战略、模式、工具和实施路线图,以帮助制造企业实现数字化转型的目标。 一、集团制造企业数字化转型的战略 制造企业数字化转型的...
发展趋势显示,虽然全面的双活数据中心尚在发展中,多数银行倾向于实现应用双活,即在主中心和灾备中心之间进行应用层面的同步,而数据库通常采取主备模式,读写操作集中在主中心。更高级的多活数据中心则能实现...
综上所述,华为的双活数据中心解决方案通过创新的技术和架构设计,实现了业务连续性和数据保护的双重目标,为企业提供了一种高效、可靠的灾备策略。在当前数字化转型的背景下,这种解决方案对于保障关键业务的稳定...
双活数据中心是现代企业信息化建设中的重要组成部分,旨在提供连续性的业务服务,避免单点故障导致的数据丢失或...通过存储、计算、应用和网络层面的双活设计,企业可以更好地保护数据,提升服务质量,增强业务韧性。
4. **改版后果**:即使转型为导购网站,爱乐活可能也难以获得高估值的后续融资,因为美丽说和蘑菇街等类似模式的网站已经形成自己的市场地位,而爱乐活缺乏明显的竞争优势。 5. **建议方向**:文章建议爱乐活聚焦于...
IT转型的第二个阶段是以应用程式为主的运行,通过主动/备用或主动/主动的双活设计来提供高品质应用服务。主动/主动设计能够实现无缝切换,但可能受限于特定硬件厂商,而主动/备用设计虽然会产生中断,但能更有效地...
双云双活架构是一种为了提高业务连续性和数据安全性而设计的技术方案,它旨在通过在两个不同的云环境中同时运行应用和数据,以减少单一故障点的风险。本文将探讨双云双活架构的背景、挑战、技术方案、实践经验和未来...
双活数据中心的技术复杂度较高,尤其是主主模式,要求零停机、零中断,RPO(恢复点目标)和RTO(恢复时间目标)均为0,意味着数据零丢失且能实时无缝切换。双运营模式则在确保日常运行性能的同时,也要求快速响应...
本文主要探讨了双活数据库在银行业务系统中的应用,并详细描述了沃趣科技为某银行设计的双活数据中心方案。 首先,我们需要了解分布式系统的概念。分布式系统是一种计算方法,它将一个大型的应用程序或数据资源分割...
业务连续性需求促使企业从数据级灾备转向应用级容灾,这推动了同城双中心从热备模式向双活模式的转变,异地灾备中心也逐渐演变为能够处理非生产业务的中心。计算虚拟化技术的进步简化了灾备模式,加速了容灾恢复的...
《桥博盖梁活载计算原理》PPT学习教案主要探讨了桥梁工程中关于盖梁活载计算的传统简化算法和现代直接加载法,以及设计师在实际应用中的算法流程。以下是详细的知识点解析: 1. 传统简化算法: - 对称布载与偏心布...
混合云应用双活解决方案则侧重于使用数据中心、AWS私有云以及第三方云资源来构建双活架构。在这一架构中,企业可以实施流量路由控制、数据复制与同步以及日志监控等功能。此外,这一方案还支持自动化运维流程,包括...
企业数据中心在使用此模式时,会根据IP地址规划和配置业务,确保用户请求能够准确地导向正确的服务器。这种模式的典型部署通常包括静态IP分配、浮动IP策略等,以确保在数据中心之间的业务透明切换。 【基于DNS的...