设计模式是面向对象编程的热门话题之一,越来越多的开发人员认识到设计模式的重要性,愿意花大量的时间学习它。采用各种语言实现设计模式的文章也越来越多,但是很多开发人员发现很难将设计模式与实际开发中需要解决的具体问题相联系。因为使用设计模式的难点往往不在于模式的实现,而在于很难确定哪种模式可以在现实的应用场景中采用,从而导致了在现实的项目中,面对客户的压力、老板的指责和无休止的加班时,我们总是采用最直截了当的方法解决问题,来不及过多考虑这些方法的优劣,即使明知将带来更大的麻烦也必须如此。有些时候则因为选择了不恰当的设计模式,使原本简单的问题变得复杂化。
总是有些优秀的设计人员可以在同样短的时间内做出正确的判断,他们同样是依靠本能和直觉,只是这种本能是在日常编程开发中一点一滴积累起来的。如同一个剑客在危机时刻的一击,并不是一时的灵光乍现,而是平时刻苦修炼的结果。
因此我们需要解决的问题是将设计模式变为经验和直觉,这样确实有些困难。因为这需要对设计模式有深刻的理解,还需要有大量的编程实践作为基础,而二者本身也是相辅相成的。如何快速地掌握设计模式并真正地变为护身武器,是笔者一直试图解决的问题。
怎样掌握设计模式
笔者认为,掌握设计模式需要解决如下问题。
(1)正确理解设计模式。
模式所关注的不仅是重复的解决方案,更主要的是关注重复出现的应用场景和与场景相关的各种作用力。很多使用设计模式失败的原因,并不是实现设计模式的方法有问题,而是采用的设计模式不适合应用场景。这往往导致设计过度,使软件变得复杂,进而丧失对使用设计模式的信心。
(2)编程语言与设计模式的实现。
尽管设计模式本身并不要求一定用某种语言来实现,但脱离了具体的实现,就无法真正理解设计模式。GOF的《设计模式》是经典之作,但毕竟距现在已经十几年了。这个期间开发平台已经进化了多代,很多新技术已经应用到编程中。有些技术可以简化设计模式的实现,有些技术已经采用了设计模式。因此,学习设计模式必须针对所使用的编程语言和开发平台。一定要注意,不是将《设计模式》中的例子转换为C#或者其他语言就等于知道如何实现设计模式了,而是要关注设计模式的精髓,并结合具体的语言特点完成其实现。就.NET而言,很多技术可以简化设计模式的实现,例如,采用反射技术实现工厂和采用委托技术实现模板方法等,这些技术在本书相关章节中都有详细讨论。
(3)在实践中使用设计模式。
这是最重要的,仅靠背棋谱成不了围棋高手。只是在概念上理解设计模式而不实现,同样成不了架构设计师。在进行软件设计时,要有意识地问自己使用还是不使用设计模式,不要匆忙下结论。重视软件质量的改进,如果有可能,则在项目后期重构代码。同时注意学习同行的经验,很多开放源码项目是很值得学习的。
这里需要注意的是,一定不要照搬教科书上的例子。
在实践中使用设计模式
(1)需求驱动。
需求驱动不仅仅是功能性需求,还包括性能需求及运行时的需求,如软件的可维护性和可复用性等方面。
设计模式是针对软件设计的,而软件设计是针对需求的,一定不要为了使用模式而使用模式。在不合适的场合生搬硬套地使用模式反而会使设计变得复杂,使软件难以调试和维护。
(2)分析成功的模式应用项目。
对现有的应用实例进行分析是学习模式的一个很好的途径,应当注意学习已有的项目不仅是学习设计模式如何实现,更重要的是注意在什么场合使用设计模式。
“置之死地而后生”可以说是一种解决方案,而不是模式,或者说仅仅给出了模式的实现,而没有交代使用的场合。项羽采用这个方案把秦军打败了,但马谡却丢了街亭。
有很多开放源码的项目可以作为学习的实例,本书的第3篇列举了多个开源项目的实例来说明设计模式的应用。
. (3)充分了解所使用的开发平台。
总的来说,设计模式是针对面向对象的软件设计的,因此在理论上适合任何面向对象的语言。但随着技术的发展和编程环境的改善,设计模式的实现方式会有很大的差别。在某些平台下,某些设计模式是自然实现的,某些模式已经被平台所实现,某些模式存在的上下文已经消失。
这里的平台不仅指编程语言,还包括平台引入的技术。.NET平台引进了反射、委托,以及类属性等新技术,这些技术的使用使设计模式的实现方式有了很大的改变。例如,工厂方法通过采用反射技术,可以将其中的子类去掉。这实际上已经是一个.NET下的新模式,或者说是.NET的“方言”。
(4)编程,编程,还是编程。
软件开发是一项实践工作,最直接的方法就是编程。没有定式很熟却从来不下棋的围棋高手,也没有不会编程就成为架构设计师的先例。对设计模式的掌握是水到渠成的事情,你可能是“顿悟”,也可能是“渐悟”,但前提是必须有相当的实践积累。当然,并不是不需要看书学习,但实践仍然是必须首先要重视的。
笔者认为编程如同写文章,提高需要有一个过程。在多多编程的同时,需要有一定的技巧。如果希望水平有较大提高,则需要对自己编写的代码不断重构。力求最优是个很好的习惯,当然前提是项目进度允许。即使项目时间紧张,也需要进行适当的总结。隔一段时间检查一下以前的工作,会发现自己是否已经有了提高。
(5)避免设计过度。
设计模式解决的是设计不足的问题,但同时也要避免设计过度。一定要牢记简洁原则(Keep It Simple,Stupid,KISS),要知道,设计模式是为了使设计简单,而不是更复杂。如果引入设计模式使设计变得复杂,只能说我们把简单的问题复杂化了,问题本身不需要设计模式。
这里需要把握的是需求变化的程度,一定要区分需求的稳定篇和可变篇。一个软件必然有稳定的篇,这个篇就是核心业务逻辑。如果核心业务逻辑发生变化,软件就没有存在的必要,这个篇的逻辑是我们需要固化的。对于可变的篇,需要判断可能发生变化的程度来确定设计策略和设计风险。要知道,设计过度与设计不足同样对项目有害。
(6)合理看待设计模式的实现实例。
现在,从各种途径可以发现各种设计模式的实现实例。需要说明的是,其中很多实例所说明的仅仅是设计模式的解决方案的实现,并没有分析模式使用的上下文。实际上,这也是最困难的篇——从而导致实例中的设计模式使用从实践的角度看,往往是过度设计,也就是有小题大做的嫌疑。实际上,本书中的有些实例也是这样。这是因为描述一个复杂应用的场合需要大量的篇幅,可能使读者花费很多的时间和精力在与自己不相干的业务描述上,但如果不如此处理,则无法使读者理解使用设计模式的必要性和难点,这样就造成了两难选择。很多讲解模式的书籍和文章只好将问题简化,可是这样的处理,虽然读者易于理解模式的实现方式,却经常忽略了分析问题的篇,或者是认为这些实例是设计过度。笔者在编写本书之前,也认为很多关于模式的文章会误导读者,因为很多场景根本没有必要使用某种设计模式,但实际编写起来,则发现几方面都照顾到确实很困难。为此需要读者在学习模式时合理看待这些实例,而不要生搬硬套。
讲解设计模式的难度
关于模式和设计模式的书已经有很多了,在国外很多书店中有关于模式的专架。然而,想通过一本书将设计模式讲解得完全透彻是一件很困难的事,原因如下。
(1)笔者所处领域的局限性。
前面已经提到了,设计模式针对的不仅仅是解决方案,更重要的是问题出现的场景。正因为如此,正确分析设计模式的应用场景是非常重要的。软件开发是需求驱动的,需求来源于解决某一方面的问题。如果读者对需求的背景不了解,就很难理解相应的软件结构和采用设计模式的必要性。可是“隔行如隔山”,向没有专业背景的读者介绍另一个领域的需求是一件很困难的事。《设计模式精解》(Design Patterns Explained)是一本不错的关于设计模式的书,但其中的实例只是针对CAD/CAM软件的。如果读者不是很了解其中的需求背景,则无法真正理解其中谈到的设计模式。
(2)笔者经验的局限性。
GOF的设计模式有23种,这是从无数实际项目中抽取出来的。但在实际项目中没有哪个项目中可以用到如此之多的模式,开发人员很少有可能在实际项目中都应用过这23种模式。某些模式可能一生也用不到(因为没有合适的使用场景),因此在介绍时就会有所偏重。
(3)示例代码的繁简程度。
设计模式在一定的设计场合下才能显示其优越性,如果把这种优越性表现出来,需要相应的代码,而在一本书中完全展现代码往往是不可能的。另一方面,如果对代码做过多的裁剪,则又不能展现完整的设计场合,从而会使读者忽略了其中的精华。GOF的《设计模式》一书之所以受欢迎,笔者认为其中的原因之一就是其繁简得当。记得最初学习工厂方法时,怎么也不明白为什么这种方法是必要的。主要原因就是当时看到的示例代码过于简单,以至于采用了工厂方法反而使设计看起来变得不必要的复杂,所以除非对实质把握得非常到位,否则采用过多的伪码反而会难以理解。
(4)现实的案例。
如果设计模式仅仅限于概念层的讨论,是不能满足解决实际项目问题的需要的。这时需要有真实的案例分析,然而做到这一点绝非易事,因为现实的案例有其特定的业务上下文。如果让读者理解为什么这个方案要采取这个模式,就需要对业务环境有比较详细的描述。而这些业务环境对于读者而言,除了理解方案,没有其他用处,除非某些读者的工作领域与作者相同。
本书的结构
本书试图使读者对设计模式有一定的了解并对实际项目有所帮助,全书分为4篇。
第1篇是面向对象设计的一些基本原则和由.NET引入的新概念。
设计模式的引入针对面向对象设计中的难点,因此,有必要了解这些难点之所以成为难点的原因。通过这一篇的示例,我们会发现设计模式的使用是自然的,也是必然的。
第2篇逐一介绍设计模式,本书以GOF的23种设计模式为主线介绍,在介绍每一种设计模式时大致采用如下的结构。
(1)从一个容易理解的需求背景入手,说明为什么要引入某一个设计模式。
(2)介绍设计模式的动机、使用环境、结构、收益与代价。
(3)介绍如何在.NET环境中实现这个模式,并给出代码框架。为了考虑到代码的完整性和篇幅之间的关系,书中仅列出代码片段。
(4)介绍应用实例,这些实例来源于实际项目,可以从不同的角度说明应用设计模式时应注意的问题。这些实例点到为止,主要说明在什么样的业务环境下可以考虑用什么样的设计模式。
(5)介绍相关模式并进行总结。
这个部分采用两种不同的代码示例,当我们讨论模式的实现时,将采用比较简单的例子,以说明如何实现模式。这些例子只是为了说明实现的方法,但并不说明模式的上下文和作用力。还有一种是从实际项目中抽取的,并且经过适当裁剪的实例。这些例子着重讨论使用模式的环境和效果,以及在选择模式时所做的妥协。笔者希望通过这两类示例能使读者通过有限的篇幅对模式有比较全面的了解。
第3篇介绍设计模式在几个开放源码中的应用。
设计模式是为软件设计服务的,单纯对单个模式的理解并不代表已经掌握了设计模式。在真实世界的项目中会面临多种问题和选择,在真正项目中学习如何使用设计模式的,会起到事半功倍的效果。
感谢开放源代码项目为我们提供了现成的示例,本书选择如下几种应用广泛的.NET开源项目。
· DotNetNuke:开放源码的Portal。
· NUnit:.NET的单元测试框架。
· Ndoc:文档生成工具。
· Gentle:对象持续化框架。
这些项目的源代码可以免费获得,并且广泛地应用在.NET开发人员的日常工作中。这样可以不用过多地介绍应用背景,即可使读者专注于设计模式的讨论。通过对这些项目中设计模式的分析,希望能使读者从整个项目的角度把握设计模式的应用。
第4篇介绍模式的更一般的概念。模式世界中不仅包括设计模式(尽管它是最流行的),还有分析模式和体系结构模式等。这一篇介绍了模式的历史、如何发现和书写模式,以及模式社会的交流场所PLoP(Pattern Language of Program,程序模式语言大会)等。本篇主要结合笔者的经历,希望对模式研究有兴趣的读者有所帮助,也希望能形成我们自己的模式社区和团体。
致谢
感谢与笔者工作过的同事和客户的支持与帮助,也感谢笔者的家人在各方面的支持。
感谢笔者在模式学习和实践过程中曾给予过帮助的同行,特别感谢笔者PLoP 2002和PLoP 2003论文的Shepherd——Ed Fernandez和Jutta Eckstein,以及参与讨论的工作组成员。
感谢电子工业出版社对笔者的信任和支持,感谢本书策划编辑朱沭红、责任编辑孙学瑛为本书所做的工作。
请与我联系
限于笔者的水平,书中错误和不妥之处在所难免,也希望读者多多批评指正。笔者的E-mail地址是zl@hzchina.net,读者也可以访问http://www.hzchina.net/patterns。