第1章 从招式与内功谈起——设计模式概述
关于金庸小说中到底是招式重要还是内功重要的争论从未停止,这里并不分析张无忌的九阳神功和令狐冲的独孤九剑到底哪个更厉害,每个武林人士梦寐以求的应该是既有淋漓的招式又有深厚的内功。软件开发技术也包括一些招式和内功:Java、C#、C++等编程语言,Eclipse、Visual Studio等开发工具,JSP、ASP.NET等开发技术,Struts、Hibernate、JBPM等框架技术,所有这些都可以认为是招式;而数据结构、算法、设计模式、重构、软件工程等则为内功。招式可以很快学会,但是内功的修炼需要更长的时间。每位软件开发人员都希望成为一名兼具淋漓招式和深厚内功的“上乘”软件工程师,对设计模式的学习与领悟会令“内功”大增,再结合日益纯熟的“招式”,软件开发“功力”一定会达到一个新的境界。既然这样,还等什么,赶快行动吧,下面就正式踏上神奇而又美妙的设计模式之旅。
1.1 设计模式从何而来
在介绍设计模式的起源之前,先要了解一下模式的诞生与发展。与很多软件工程技术一样,模式起源于建筑领域,与只有几十年历史的软件工程相比,已经拥有几千年沉淀的建筑工程有太多值得学习和借鉴的地方。
模式是如何诞生的?先来认识一个人——Christopher Alexander(克里斯托弗·亚历山大),哈佛大学建筑学博士、美国加州大学伯克利分校建筑学教授、加州大学伯克利分校环境结构研究所所长、美国艺术和科学院院士等,他还有一个“昵称”——模式之父(the father of patterns)。Christopher Alexander博士及其研究团队用了约20年的时间,对住宅和周边环境进行了大量的调查研究和资料收集工作,发现人们对舒适住宅和城市环境存在一些共同的认知规律。Christopher Alexander在著作A Pattern Language:Towns,Buildings,Construction中把这些认同规律归纳为253个模式(Pattern),对每一个模式都从前提条件(Context)、目标问题(Theme或Problem)、解决方案(Solution)三方面进行了描述,并给出了从用户需求分析到建筑环境结构设计直至经典实例的过程模型。
在Christopher Alexander的另一部经典著作The Timeless Way of Building(《建筑的永恒之道》)中,他给出了关于模式的定义:每个模式都描述了一个在我们的环境中不断出现的问题,然后描述了该问题的解决方案的核心,通过这种方式,我们可以无数次地重用那些已有的成功的解决方案,无须再重复相同的工作。这个定义可以简单地用一句话表示:
模式是在特定环境下人们解决某类重复出现问题的一套成功或有效的解决方案。(A pattern is a successful or efficient solution to a recurring problem within a context.)
1990年,软件工程界开始关注Christopher Alexander等在这一住宅、公共建筑与城市规划领域的重大突破。最早将模式的思想引入软件工程方法学的是1991—1992年以“四人组(Gang of Four,简称GoF,分别是Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides)”自称的4位著名软件工程学者,他们在1994年归纳发表了23种在软件开发中使用频率较高的设计模式,旨在用模式来统一沟通面向对象方法在分析、设计和实现间的鸿沟。
GoF将模式的概念引入软件工程领域,这标志着软件模式的诞生。软件模式(Software Patterns)是将模式的一般概念应用于软件开发领域,即软件开发的总体指导思路或参照样板。软件模式并非仅限于设计模式,还包括架构模式、分析模式和过程模式等,实际上,在软件开发生命周期的每一个阶段都存在着一些被认同的模式。
软件模式是指在软件开发过程中某些可重现问题的有效解决方法,其基础结构主要由四部分构成,包括问题描述(待解决的问题是什么)、前提条件(在何种环境或约束条件下使用)、解法(如何解决)和效果(有哪些优缺点),如图1-1所示。
图1-1 软件模式基本结构
软件模式与具体的应用领域无关,也就是说无论是移动应用开发、桌面应用开发、Web应用开发还是嵌入式软件的开发,都可以使用软件模式。
在软件模式中,设计模式是研究最为深入的分支。设计模式用于在特定的条件下为一些重复出现的软件设计问题提供合理的、有效的解决方案。它们融合了众多专家的设计经验,已经在成千上万的软件中得以应用。1995年,GoF将收集和整理好的23种设计模式汇编成Design Patterns:Elements of Reusable Object-Oriented Software(《设计模式:可复用面向对象软件的基础》)一书,该书的出版也标志着设计模式正式成为面向对象(Object Oriented)软件工程的一个重要研究分支。
从1995年至今,无论是在大型API或框架(例如JDK、.NET Framework等)、轻量级框架(例如Struts、Spring、Hibernate、JUnit等),还是应用软件的开发中,设计模式都得到了广泛应用。正在从事面向对象开发或正准备从事面向对象开发的技术人员,无论是使用Java、C#、Objective-C、VB.NET、Smalltalk等纯面向对象编程语言,还是使用C++、PHP、Delphi、JavaScript、Python等可支持面向对象编程的语言,都应该懂一点设计模式。
1.2 设计模式是什么
俗话说:站在别人的肩膀上,我们会看得更远。利用设计模式,通过一些成熟的设计方案来指导新项目的开发和设计,便能开发出既具有良好的灵活性和可扩展性,又易于复用的软件系统。
设计模式的一般定义如下:
设计模式(Design Pattern)是一套被反复使用的、多数人知晓的、经过分类编目的代码设计经验的总结,使用设计模式是为了可以重用代码,让代码更容易被他人理解并且提高代码的可靠性。
狭义的设计模式是指GoF在Design Patterns:Elements of Reusable Object-Oriented Software(《设计模式:可复用面向对象软件的基础》)一书中所介绍的23种经典设计模式。不过设计模式并不仅仅只有这23种,随着软件开发技术的发展,越来越多的新模式不断诞生并得以应用。本书将主要围绕GoF 23种模式进行讲解。
设计模式一般包含模式名称、问题、目的、解决方案、效果等组成要素,其中关键要素是模式名称、问题、解决方案和效果。模式名称(Pattern Name)通过一两个词来为模式命名,以便我们更好地理解模式并方便开发人员之间的交流,绝大多数模式都是根据其功能或模式结构来命名的(GoF设计模式中没有一个模式用人名命名);问题(Problem)描述了应该在何时使用模式,它包含了设计中存在的问题以及问题存在的原因;解决方案(Solution)描述了一个设计模式的组成部分,以及这些组成部分之间的相互关系、各自的职责和协作方式,通常解决方案通过UML类图和核心代码进行描述;效果(Consequence)描述了模式的优缺点以及在使用模式时应权衡的问题。
虽然GoF设计模式只有23个,但是它们各具特色,每个模式都为某一类可重复的设计问题提供了一套解决方案。根据它们的用途,设计模式可分为创建型(Creational)、结构型(Structural)和行为型(Behavioral)3种。其中,创建型模式主要用于描述如何创建对象;结构型模式主要用于描述如何实现类或对象的组合;行为型模式主要用于描述类或对象怎样交互以及怎样分配职责。在GoF 23种设计模式中,包含5种创建型设计模式、7种结构型设计模式和11种行为型设计模式。此外,根据某个模式主要是用于处理类之间的关系还是用于处理对象之间的关系,设计模式还可以分为类模式和对象模式。经常将两种分类方式结合使用,如单例模式是对象创建型模式,模板方法模式是类行为型模式。
值得一提的是,有一个设计模式虽然不属于GoF 23种设计模式,但一般在介绍设计模式时都会对它进行说明,它就是简单工厂模式,也许是太“简单”了,GoF并没有把它写到那本经典著作中,不过现在大部分的设计模式书籍都会对它进行专门的介绍,本书也不例外。
表1-1列出了本书将要介绍的24种设计模式。其中,模式的“学习难度”和“使用频率”是笔者在多年模式使用和推广过程中的经验总结,供大家参考。
表1-1 常用设计模式一览表
1.3 设计模式有什么用
简单来说,设计模式至少有如下几个用途:
(1)设计模式来源于众多专家的经验和智慧,它们是从许多优秀的软件系统中总结出的成功的、能够实现可维护性复用的设计方案,使用这些方案可以避免做一些重复性的工作,有助于提高设计和开发效率。
(2)设计模式提供了一套通用的设计词汇和一种通用的形式来方便开发人员之间进行沟通和交流,使得设计方案更加通俗易懂。交流通常很耗时,任何有助于提高交流效率的东西都可以为开发人员节省不少时间。无论使用哪种编程语言,做什么类型的项目,甚至一个国际化的开发团队,当面对同一个设计模式时,大家的理解并无歧义,因为设计模式是跨语言、跨平台、跨应用、跨国界的。
(3)大部分设计模式都兼顾了系统的可重用性和可扩展性,这使得开发人员可以更好地重用一些已有的设计方案、功能模块甚至一个完整的软件系统,避免经常做一些重复的设计、编写一些重复的代码。此外,随着软件规模的日益增大,软件寿命的日益变长,系统的可维护性和可扩展性也越来越重要,许多设计模式将有助于提高系统的灵活性和可扩展性,在不修改或者少修改现有系统的基础上增加、删除或者替换功能模块,如果一点设计模式都不懂,要做到这一点还是很困难的。
(4)合理使用设计模式并对设计模式的使用情况进行文档化,将有助于别人更快地理解系统。对于离职人员的项目,只要接手人员也懂设计模式,就能够很快理解该项目的设计思路和实现方案,使得后续工作顺利进行。
(5)最后一点对初学者很重要,学习设计模式将有助于初学者更加深入地理解面向对象思想。例如,如何将代码分散在几个不同的类中?为什么要有“接口”?何谓针对抽象编程?何时不应该使用继承?如何不修改源代码增加新功能?同时还能够更好地阅读和理解现有类库(如JDK)与其他系统中的源代码,早点脱离面向对象编程的“菜鸟期”。
1.4 个人观点
作为设计模式的忠实粉丝和推广人员,在正式开始本书的学习之前,我结合多年的模式应用和教育培训经验与大家分享几点个人看法,以供参考。
(1)掌握设计模式并不是件很难的事情,关键在于多思考,多实践,要对自己有信心。
(2)在学习每个设计模式时至少应该掌握如下几点:这个设计模式的意图是什么,它要解决一个什么问题,什么时候可以使用它;它是如何解决问题的,掌握它的结构图,记住它的关键代码;能够想到至少两个它的应用实例,一个生活中的,一个软件中的;这个模式的优缺点是什么,在使用时要注意什么。当你能够回答上述所有问题时,恭喜你,你了解一个设计模式了,至于掌握它,那就在实际开发中去使用吧,用多了自然就掌握了。
(3)“如果想体验一下运用模式的感觉,那么最好的方法就是运用它们。”正如本章最开始所说的,设计模式是“内功心法”,它还是要与“实战招式”相结合才能够相得益彰。学习设计模式的目的在于应用,如果不懂如何使用一个设计模式,而只是学过,能够说出它的用途,绘制它的结构,充其量也只能说你了解这个模式,严格一点说:不会在开发中灵活运用一个模式基本上等于没学。所以一定要做到:少说多做。
(4)千万不要滥用模式,不要试图在一个系统中用上所有的模式,也许有这样的系统,但至少到目前为止笔者还没有碰到过。每个模式都有自己的适用场景,不能为了使用模式而使用模式,滥用模式不如不用模式,因为滥用的结果不仅得不到“艺术品”一样的软件,还很有可能是一堆垃圾代码。
(5)如果将设计模式比喻成“三十六计”,那么每一个模式都是一种计策,它为解决某一类问题而诞生,不管这个设计模式的难度如何,使用频率高不高,我建议大家都应该好好学学,多学一个模式也就意味着多了“一计”,说不定什么时候一不小心就用上了。因此,模式学习之路上要不怕困难,勇于挑战,有的模式虽然难一点,但反复琢磨,反复研读,应该还是能够征服的。
(6)设计模式的“上乘”境界:“手中无模式,心中有模式。”模式使用的最高境界是你已经不知道具体某个设计模式的定义和结构了,但你会灵活自如地选择一种设计方案(其实就是某个设计模式)来解决某个问题。设计模式已经成为你开发技能的一部分,能够手到擒来,“内功”与“招式”已浑然一体。这个境界并不是看完某本书或者开发一两个项目就能够达到的,它需要不断沉淀与积累。所以,对模式的学习不要急于求成。
(7)最后一点来自GoF已故成员、我个人最尊敬和崇拜的软件工程大师之一John Vlissides的著作Pattern Hatching Design Patterns Applied(《设计模式沉思录》):模式从不保证任何东西,它不能保证你一定能够做出可复用的软件,提高你的生产率,更不能保证世界和平。模式并不能替代人来完成软件系统的创造,它们只不过会给那些缺乏经验但却具备才能和创造力的人带来希望。
扩展
John Vlissides(1961—2005),GoF成员,斯坦福大学计算机科学博士,原IBM研究员,因患脑瘤于2005年11月24日(感恩节)病故,享年44岁,为纪念他的贡献,ACM SIGPLAN特设立John Vlissides奖。