9、设计模式概论

  设计模式主要应用于面向对象软件设计领域,对于面向对象编程也有很好的指导意义。很多人都是通过对设计模式的学习和掌握才真正理解面向对象的。很多具有多年开发经验的Java或C#程序员,它们一直采用面向对象语言来从事软件开发,但是基本上还是按照传统的结构化编程方式,不理解抽象类和接口有什么作用,不明白什么时候该用类继承什么时候该用对象关联,通过对设计模式的学习,他们才真正领悟到面向对象的魅力,更好地从事面向对象设计与编码工作。

  设计模式来源于众多专家的经验和智慧,是从许多优秀的软件系统中总结出的成功的、目的是为了更好的实现可维护性复用的设计方案,设计模式是在特定的环境下为解决某一通用软件设计问题提供的一套定制的解决方案,该方案描述了对象之间的相互作用,他是一套被反复用的、多数人知晓的、经过分类编目的、代码设计经验的总结。我们知道,随着软件寿命的延长和软件规模的扩大。如何更好地维护软件和实现软件的复用是现在软件界面临的一个很总要的问题,越来越多的时间、经历和金钱花费在软件的维护上,从某种意义上来说,设计模式的使用可以避免我们做一些重复性的工作,有助于提高设计和开发效率。

  此外设计模式提供了一套标准的设计词汇,方便开发人员之间交流,例如学过设计模式的人都知道适配器是怎么回事,都知道桥接模式应用于什么样的环境,都知道单例如何实现,也都知道迭代器是用来干啥的。除此之外,设计模式对于提高系统的可重用性、可扩展性以及设计方案的文档化具有重要的意义,对于面向对象的初学者而言,提高学习和掌握一些设计模式的知识有助于更好地理解面向对象的三大特性:封装、继承和多态,同时还能够帮助大家更好地阅读理解现有类库和框架的源码,降低学习成本。

设计模式的诞生于定义

  模式起源于建筑业而非软件业

  模式(Pattern)之父--美国加利福尼亚大学环境结构中心研究所所长Christopher Alexander博士

  《A Pattern Language:Towns,Buildings,Construction》--253个建筑和城市规划模式

模式

  Context(模式适用的前提条件)

  Theme或Problem(在前提条件下要解决的目标问题)

  Solution(对目标问题求解过程中各个物理关系的记述)

  1990年,软件工程界开始关注Christopher Alexander等在住宅、公共建筑与城市规划领域的重大突破,最早将该模式的思想引入软件工程方法学的是1991-1992年以“四人组(Gang of Four,GoF,分别是Erich Gamma Richard Helm,Ralph Johnson和John Vlissides)"自称的四位著名软件工程者,他们在1994年归纳发表了软件开发中使用频率最高的设计模式,意在用模式来统一沟通面向对象方法在分析、设计和实现之间的鸿沟。

  软件模式是将模式的一般概念应用于软件开发领域,即软件开发的总体指导思路或参照样板。软件模式并非仅限于设计模式,还包括架构模式、分析模式和过程模式等,实际上,在软件生存期的每个阶段都存在着一些被认同的模式。

  软件模式可以认为是对软件开发这一特定”问题“的”解法“的某种统一表示,它和Alexander所描述的模式定义完全相同,即软件模式等于一定条件下的出现的问题以及解法。软件模式的基础结构有4个部分构成:问题描述、前提条件(环境或约束条件)、解法和效果。

 

  软件模式与具体的应用领域无关,在模式发现过程中需要遵循三大律(Rule of Three),即只有经过三个以上不同类型(或不同领域)的系统校验,一个解决方案才能从候选模式升格为模式。

  每个模式描述了一个我们周围不断重复发生的问题,以及该问题的解决方案的核心 --Christopher Alexander

  人是经验性的物种。设计模式描述了软件设计过程中某一类常见问题的一般性的解决方案。软件设计模式是软件设计领域设计经验的归纳与汇集

  事实上,很多行业都有自己的设计模式。就算设计模式本身不是起源于软件行业,而是起源于建筑行业。

  设计模式概念:设计模式是经过验证的,在特定的环境下重复出现的,针对特定问题的程序解决方案。

  从1995年至今,设计模式在软件开发中得以广泛应用,在Sun的Java SE/Java EE平台和Microsoft的.net平台设计中就用了大量的设计模式。

  诞生了越来越多的与设计模式相关的书籍和网站,设计模式也作为一门独立的课程或作为软件体系结构等课程的重要组成部分出现在国内外研究生和大学生的课堂上。

  设计模式一般有如下几个基本要素:模式名称、问题、目的解决方案、效果、实例代码和相关设计模式,其中的关键元素包括以下几个方面:

    设计名称(Pattern name)

    问题(Problem)

    解决方案(Solution)

    效果(Consequences)

 

 

创建型模式

  抽象工厂模式(Abstract Factory)

  建造者模式(Builder)

  工厂方法模式(Factory Method)

  原型模式(Prototype)

  单例模式(Singleton)

结构型模式

  适配器模式(Adapter)

  桥接模式(Bridge)

  组合模式(Composite)

  装饰模式(Decorator)

  外观模式(Facade)

  享元模式(Flyweight)

  代理模式(Proxy)

行为型模式

  职责链模式(Chain of Responsibility)

  命令模式(Command)

  解释器模式(Interpreter)

  迭代器模式(Iterator)

  中介者模式(Mediator)

  备忘录模式(Memento)

  观察者模式(Observer)

  状态模式(State)

  策略模式(Strategy)

  模板方法模式(Template Method)

  访问者模式(Visitor)

游戏中常用模式

  单例模式、工厂模式、观察者模式、代理模式、命令模式、适配器模式、合成模式、访问者模式

设计模式的优点

  设计模式是从许多优秀的软件系统中总结出的成功的、能够实现可维护性复用的设计方案,使用这些方案将避免我们做一些重复性的工作,而且可以设计出高质量的软件系统。

  设计模式融合了众多专家的经验,并以一种标准的形式供广大开发人员所用,它提供了一套通用的设计词汇和通用的语言以便开发人员之间沟通和交流,使得设计方案更加通俗易懂。对于使用不同编程语言的开发和设计人员可以通过设计模式来交流系统设计方案,每个设计模式都对应一个标准的解决方案,设计模式可以降低开发人员理解系统的复杂度。

  设计模式使人们更加简单方便地复用成功的设计和体系结构,将已证实的技术表述成设计模式也会使系统开发者更容易理解其设计思路。设计模式使得重用成功的设计更加容易,并避免那些导致不可重用的设计方案。设计模式使得设计方案更加灵活,且易于修改。

  设计模式的使用将提高软件系统的开发效率和开发质量,在一定程度上节约设计成本。

  设计模式有助于初学者更深入的理解面向对象的思想,一方面可以帮助初学者更加方便的阅读和学习现有类库与其他系统的源代码,另一方面还可以提高软件的设计水平和代码质量。

Gof 23种设计模式

  历史性著作《设计模式:可复用面向对象的基础》一书中描述了23中经典面向对象设计模式,创立了设计模式在软件设计中的地位。

  由于《设计模式》一书确定了设计模式的地位,通常所说的设计模式隐含地表示”面向对象设计模式“,但这并不意味”设计模式“就等于”面向对象设计模式“,也不意味着23种设计模式就是面向对象设计模式的起点,非终点。

面向对象与设计模式

  面向对象设计模式解决的是”类与相互通信的对象之间的组织关系,包括他们的角色、职责、协作方式几个方面。

  面向对象设计模式是“好的面向对象设计”是那些可以满足“应对变化,提高复用”的设计。

  面向对象设计模式不像算法技巧,可以照搬照用,它是建立在对’面向对象“纯熟、深入理解的基础上经验性认识。掌握面向对象设计模式的前提是首先掌握”面向对象“!

恰当使用设计模式

  ”什么时候、什么地方应用设计模式"比”理解设计模式本身“更为重要。设计模式的应用不宜先入为主,一上来就使用设计模式是对设计模式的最大误用。没有一步到位的设计模式。

  现代软件设计的特征是”需求的频繁变化“。如果没有需求变化,便没有面向对象,更没有”面向对象设计模式“。设计模式的要点便是”寻找变化点,然后在变化点处应用设计模式,从而来更好的应对需求的变化

  敏捷软件开发实践提倡的“Refactoring to Patterns(重构与模式)“是目前普遍公认的最好的使用设计模式的方法。

用设计模式组织代码

  学设计模式,是为了学习如何合理的组织我们的代码,如何解耦,如何真正达到对修改封闭对扩展开放的效果。

  设计模式要是真的学会了,你们会发现在写代码的时候,脑子里根本没有什么设计模式,你都已经融会贯通了。代码写完了一看,这里有模式,那里也有模式。这就如同我们讲话不会去考虑语法。但是说出来的大部分的话都是符合语法要求的。这也如同我们写程序的时候不会总是去想程序的语法问题,我们自然而然写出来的东西就是可以编译的。道理都是一样的。

设计模式的扩展点

  不过为了合理的利用设计模式,我们应该明白一个概念,叫做扩展点扩展点不是天生就有的,而是设计出来的。我们设计一个软件架构的时候,我们也要同时设计一下哪些地方可以修改,哪些地方以后不能改。倘若你的设计不能满足现实世界的需要,那你就要重构,把有用的扩展点加进去,把没有用的扩展点去除掉。这跟你用不用设计模式没有关系。

  设计模式归根揭底就是因为你使用的程序语言的抽象能力不足才发明出来的。譬如那个Listener模式,在C#里面就是一个event关键字搞定,你不需要去写一大堆的框架代码来增加这个扩展点。相反,你在Java里面就需要这么做。因此你觉得Listener模式在Java中有用,在C#中没用,其实不是这样的。

学习设计模式方法:学习->应用->总结->学习

  C#创始人Anders Hejlsberg很牛,他帮你把这个设计模式做进了语法,你不需要痛苦地写一大堆框架代码就可以用了,这种东西就叫语法糖

  那到底我们怎样才能学会设计模式呢?答案只有一个,就是创造条件去使用设计模式。很多人觉得通过简单的程序和例子来学设计模式。这是不对的。设计模式就是因为情况复杂了才会出现的,所以我们只能通过复杂的程序来学习设计模式。你不管看别人的程序也好,自己写程序练也好,那必须要复杂,复杂到你不用设计模式就做不下去,这才能起到学习设计模式的作用。

  学习-应用-总结-学习

学习设计模式的层次

  基本入门级

    要能够正确理解和掌握每个设计模式的基本知识,能够识别在什么场景下、出现了什么样的问题、采用何种方案来解决它,并能够在实际的程序设计和开发中套用相应的设计模式。

  基本掌握级

    除了具备基本入门级的要求外,还要求能够结合实际应用的场景,对设计模式进行变形使用。

    事实上,在实际开发中,经常会碰到与标准模式的应用场景有些不一样的情况,此时要合理地使用设计模式,就需要对它们做适当的变形,而不是僵硬地套用了。当然进行变形的前提是要能准确深入地理解和把握设计模式的本质,万变不离其宗,只有把握住本质,才能确保正确变形使用而不是误用。

  深入理解和掌握级

    除了基本掌握级的要求外,更主要的是:

    要从思想上和方法上吸收设计模式的精髓,并融入到自己的思路中,在进行软件分析和设计的时候,能随意地、自然而然的应用,就如同自己思维的一部分。比较复杂的应用中,当解决某个问题的时候,很可能不是单一应用某个设计模式,而是综合应用很多设计模式。例如,结合某个具体的情况,可能需要把模式A进行简化,然后结合模式B的一部分,再组合应用变形的模式C,如此来解决实际问题。

  简单点来说基本入门级就是套用使用,相当于依葫芦画瓢,很机械;基本掌握级就能变形使用,比基本入门级灵活一些,可以适当变形使用;深入理解和掌握级才算是真正将设计模式的精髓吸收了,是从思想和方法的层面上去理解和掌握设计模式,就如练习武功到最高境界”无招胜有招“了。

简单是设计的简单

  “简单”不是功能的简单,而是设计的简单。简单的设计意味着缺少灵活性,代码刚硬,只在这个项目里有用,拿到其他项目中就是无用,我将其称之为“一次性代码”。

  要使用代码被反复使用,请用“设计模式”对你的代码进行设计。很多我所认识的程序员在接触到设计模式之后,都有一种相见恨晚的感觉,有人形容学习了设计模式之后感觉自己已经脱胎换骨,达到了新的境界,还有人甚至把是否了解设计模式作为程序员划分水平的标准。

  我们也不能陷入设计模式的陷阱,为了使用设计模式而去套模式,那样会陷入形式主义。我们使用模式的时候,一定要注意模式的意图(intent),而不要过多的去关注模式的实现细节,因为这些实现细节特定情况下,可能会发生一些改变。不要顽固地认为设计模式一书中的类图或实现代码就代表了模式本身。

为什么会有设计模式---软件设计固有的复杂性

  建筑商从来不想给一栋已建好的100层高的楼底下再修建一个小地下室--这样做花费极大而且注定失败。然而令人惊奇的是,软件系统的用户在要求做出类似的改变时不会仔细考虑,而且他们认为这只是需要简单编程的事。--Grady Booch

复杂性的几个诱因

  问题领域的复杂性

    客户要求本身就很复杂,客户与开发人员相互不理解

  管理开发过程的困难

    开发是由人完成的,人的组织、潜能存在巨大的复杂性。

  软件可能的灵活性

    软件为开发人员提供了极大的灵活性,而开发人员也很容易滥用这种灵活性。

  表征离散系统行为困难

    软件系统本质上是一个离散系统,其复杂度要远远大于连续系统。一个简单的外部事件可能破坏整个系统。

软件设计复杂的根本原因

  软件设计变化的根本原因在于:变化

    客户需求的变化、技术平台的变化、开发团队的变化、市场环境的变化....

如何解决复杂性?

  分解:人们面对复杂性有一个常见的作法:即分而治之,将大问题分解为多个小问题,将复杂问题分解为多个简单问题

  抽象:更高层次来讲,人们处理复杂性有一个通用的技术,即抽象。由于不能掌握全部的复杂对象,我们选择忽视它的非本质的细节,而去处理泛化和思想化了的对象模型。

重新认识面向对象

  对于前面的例子,从宏观层面上来看,面向对象的构建方式更能适应软件的变化,能将变化带来的影响减为最小。

  从微观层面上看,面向对象的方式更强调各个类的“责任”,新增员工类型不会影响原来员工类型的实现代码--这更符合真实的世界,也能控制变化所影响的范围。

  

 某人应用商业模式,在路边摆地摊。这个模式需要经营场所,有库存,要进货,要销售,中间有资金流。另一个也用商业模式,比如西单商场。用的是同一模式,但问题来了,摆地摊的,绝对玩不转一个大商场。这就是为什么我们能懂得23种设计模式,却无法设计出一个.net框架,甚至I/O流的原因。

设计与重构

  设计模式是重构的目标,重构是达到目标的手段。

  重构并不排斥提前设计,重构并不是不需要设计

  合理的提前设计 + 重构来不断的改进设计 = 趋近于优秀的系统

  避免过度设计

设计模式概括性总结

  设计模式一旦找到学习的感觉和有效的方式,学习起来并不困难,而且学习的过程会很有趣,也很有成就感。但是很多人学完了并不知道怎么在项目中运用设计模式,对于学习,我想强调的是,每一个设计模式至少应该掌握如下几个点:这个设计模式的意图是什么,什么时候可以使用它;他是如何解决的,掌握它的结构图,记住它的关键代码;能够想到至少它的两个应用实例,一个生活中的,一个软件中的;这个模式的优缺点是什么,在使用时要注意什么。当你能够回答上述所有问题时,恭喜你,你了解一个设计模式了。

  注意,我用的是“了解”,并不代表你掌握这个设计模式了,如果不懂如何使用一个设计模式,而只是学过,能够说出用途,绘制它的结构,充其量也只能说你了解这个模式,严格一点说:不会再开发中灵活运用一个模式基本上等于没学。设计模式是"内功心法",它还是要与“实战招式”相结合才能够相得益彰。学习设计模式的最终目的还是在于应用,如果要做到熟练运用每个设计模式,我的建议如下:

  深入理解每个设计模式的动机,除了熟悉它的标准结构,还要熟悉它的一些常用变化,以及一些常用的模式联动机制,例如有的模式可以简化、有的经常会和别的模式一起使用。在学习设计模式之前,一定要深入理解面向对象设计原则,包括SOLID、合成复用原则以及迪米特发则,因为绝大多数设计模式都体现了一种或多种面向对象设计原则,理解面向对象设计原则是学好设计模式的基础。初此之外,还要熟悉设计模式的适用场景,知道什么时候以及什么时候不用这个设计模式,这个很关键,不能滥用设计模式。

  除了一些生活示例外,一定要看看已有的软件中模式的应用实例,例如对于Java开发人员而言,我觉得一个很好的学习设计模式的方式是结合JDK一起学习,JDK类库中应用了所有的设计模式,例如JavaIO中用到了装饰模式,AWT/Swing中用到了组合模式、抽象工厂模式,Runtime等类用到了单例模式,事件处理中用到了观察者模式,内置了迭代器等,而且很多现有的开源类库中就用到了很多设计模式,例如JUnit、Struts、Spring等框架中就用到了很多设计模式,例如在Junit中使用了组合模式、模板方法模式等设计模式。初此之外,还可以下载一些明确提到使用了设计模式的软件的源代码看看,别人是如何使用设计模式的。

软件架构、重构与设计模式

  软件架构是软件工程一个重要的分支,随者软件规模的扩大和软件寿命的延长,软件架构也越发重要。就像建筑领域,盖一个狗窝不需要进行分析与设计,但如果要盖一座万人体育场或者摩天大楼,那一定离不开设计师。软件工程与之同理,好的架构能够决定软件的成败。软件架构不只是简单的分层或者是划分模块,包括更多的内容,例如需求确认、系统分解、架构风格的选择(B/S还是C/S)、技术选型(Java还是.net,Oracle还是MySQL等)、物理架构设计、数据架构设计、逻辑架构设计等等,通常架构师还要参与包设计、核心模块设计以及类设计等概要设计和详细设计工作。正因为软件架构涉及的内容相当多,因此考虑的因素也很多。

重构与设计模式间有什么关系

  软件开发大师Robert C.Martin在《程序员的职业素养》(The Clean Coder)一书中提到要勇于整理代码,通过整理代码让它们更容易理解,更易于修改,也更易于扩展。这样代码的bug也会随之减少,不应该让代码劣化而视若不见。我觉得Rob大叔说的很好,这就是重构的意义。因此我常常跟学生们说,如果想成为一个合格的面向对象的程序员,设计模式和重构是你必须要学的两项基本技能

  重构和设计模式之间关系也很紧密,在Martin Flowler的《重构》一书中也提到了几种使用设计模式来进行重构的手段。另外还有一本书,名为《重构与模式》(Refactoring to Patterns,个人感觉翻译为“面向模式的重构”或者“重构到模式”更合适,作者是Joshua Keriewvsk,这本书获得过第15界Jolt生产效率大奖。

那么什么情况下才需要考虑重构?衡量是否重构的标准是什么?重构的难题有哪些?

  在Robert C.Martin新近出版的《程序员的职业素养》(The Clean Coder)一书中提到:当你发现修改软件不像你预想的那样简单时,你便应该改进设计,到最后不得不重构时发现代码已经很僵化。因此,要将重构变成一种习惯,每一次在阅读代码或检入代码时都要考虑对代码进行改善与优化。如果你希望自己代码灵活可变,那就应该时常修改它!我觉得这就是对扩展时机的最好回答。如果你发现代码太僵硬,不易扩展(你可以扩展一下试试),特别是那些在需求文档或者客户嘴里多次提到的需要改变的地方,一定要尽量灵活,当缺少这种灵活时,你应该马上重构。我个人觉得,衡量是否重构的标准就是现有设计方案或者代码是否容易扩展,复用性是否存在问题,将来是否会增加维护成本。为了将来,应该重构

  很多人明知设计方案或者代码有问题,却不愿意去重构,这里面也存在很多原因,我觉得很多人之所以害怕重构,最大得问题在于重构之后会引入新的问题,难于测试关于这一点,尽量实现测试得自动化。由于重构主要还是代码级的,对应的测试也主要是单元测试,单元测试的自动化程度在所有测试中应该是最高的,因此只要我们有一套完整的测试用例,重构之后可以对应修改少许的测试代码就可以马上实施回归测试了。我个人觉得这个问题还是可以解决的,但对于测试代码不完善或者根本没有测试代码的项目,实现起来恐怕会比较麻烦。因此,培养良好的开发习惯很重要。

  

 

 

  

 

posted @ 2020-06-06 16:36  三里路异乡客  阅读(276)  评论(0编辑  收藏  举报