设计模式Gof
目录
庞杂的软件模式中,分为设计模式,分析模式,组织和过程模式,本文来分析设计模式,着重理解,具体定义可参考(菜鸟教程-设计模式)
1.概念
- 定义:模式是表示特定的情景,动机,解决方案三个方面关系的规则,每个模式描述了一个在某种特定情景下不断重复发生的问题,以及该问题解决方案的核心所在。模式既是一个事物又是一个过程,不仅描述该事物本身,而且提出了通过怎样的过程来产生该事物
- 作用:简化并加快设计,方便开发人员的通信,降低风险,有助于转到面向对象技术
- 特性:巧妙,通用,实践证明,简单,可重用,面向对象
- 组成元素:模式名,问题,情景,动机,解决方案,示例,结果情景,基本原理,相关模式,已知应用
范围准则
类模式处理类和子类之间的关系,这些关系通过继承建立,是静态的,在编译时刻便确定下来了,对象模式处理对象间的关系,这些关系在运行时刻是可以变化 ,更具动态性。创建型类模式将对象的部分创建工作延迟到子类,而创建型对象模式则将它延迟到另一个对象中。结构型类模式使用继承机制来组合类,而结构型对象模式则描述 了对象的组装方式。行为型类模式使用继承描述算法和控制流,而行为型对象模式则描述 一组对象怎样协作完成单个对象所无法完成的任务
模式关系图
2 设计模式原则
-
开闭原则(Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。 -
里氏代换原则(Liskov Substitution Principle)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科 -
依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。 -
接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。 -
迪米特法则(最少知道原则)(Demeter Principle)
为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。 -
合成复用原则(Composite Reuse Principle)
原则是尽量使用合成/聚合的方式,而不是使用继承。
3.创建型模式
针对接口编程,而不是针对实现编程,不将变量声明为某个特定的具体类的实例对象,而是让它遵从抽象类所定义的接口,当你不得不在系统的某个地方实例化具体的类(即指定一个特定的实现)时,创建型模式可以帮你。通过抽象对象的创建过程,这些模式提供不同方式以在实例化时建立接口和实现的透明连接。 创建型模式抽象了实例化过程,帮助系统独立于如何创建、组合和表示它的那些对象,随着系统演化得越来越依赖于对象的复合而不是类继承,创建型模式变得更为重要。重心从对一组固定行为的硬编码转移为定义一个较小的基本行为集,这些行为可以被组合成任意数目的更复杂的行为。
3.1 抽象工厂模式 Abstract Factory
抽象工厂模式是围绕一个超级工厂创建其他工厂,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类,每个生成的工厂都能按照工厂模式提供对象。
- 意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类
- 动机:客户仅与抽象类定义的接口交互,而不使用特定的具体类的接口
- 适用场景
- 系统独立于产品的创建,组合以及表示
- 系统要由多个产品系列中的一个来配置时,当你要强调一系列相关的产品对象的设计以便进行联合使用时
- 相关产品对象系列是共同使用的,而且必须确保这一点
- 希望提供产品的类库,只开放其接口,而不是其实现
典型应用
- QQ 换皮肤,一整套一起换。
- 生成不同操作系统的程序
- 优点:分离了具体类,一个工厂封装创建产品对象的责任和过程,它将客户与类的实现分离,产品的类名不出现在客户代码中。有利于产品一致性,一个系列中的产品对象被设计成一起工作时。
易于交换产品系列,容易改变一个应用的具体工厂,仅需转换到相应的工厂对象并重新创建接口即可,客户代码不用调整。 - 缺点:在于难于扩展抽象工厂以生产新种类的产品,因为这涉及抽象工厂类及其所有子类的改变
- 类图:
3.2 构建器模式 Builder
使用多个简单的对象一步一步构建成一个复杂的对象,与工厂模式的区别是:建造者模式更加关注与零件装配的顺序
- 意图:将复杂对象的构建与其表示相分离,这样相同的构造过程可以创建不同的对象
- 动机:主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
- 适用场景
- 创建繁杂对象的算法独立于组成对象的部分以及这些部分的集合方式
- 构造过程必须允许已构建对象有不同的表示
- 将变与不变分离开
典型应用:
-
去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"
-
JAVA 中的 StringBuilder
-
优点: 可以对产品的内部表示进行改变,将构造代码与表示代码相分离
-
缺点: 产品必须有共同点,范围有限制。 如内部变化复杂,会有很多的建造类。
-
类图:
3.3 工厂方法模式 Factory Method
在工厂模式中,在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。凡是出现了大量的产品需要创建,并且具有共同的接口时,可以通过工厂方法模式进行创建。有三种具体模式中,第一种如果传入的字符串有误,不能正确创建对象,第三种相对于第二种,不需要实例化工厂类,所以,大多数情况下,我们会选用第三种——静态工厂方法模式
- 意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行
- 动机:要解决接口选择的问题
- 适用场景:
- 类不能预料它必须创建的对象的类
- 类希望其子类指定它要创建对象
- 类将责任转给某个帮助子类,而用户希望定位那个被授权的帮助子类
典型应用:
-
日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
-
数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
-
优点:没有了将应用程序类绑定到代码中的要求,代码只处理接口,允许子类提供对象的扩展版本
-
缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖;类的创建依赖工厂类,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则
-
类图:
3.4 原型模式 Prototype
实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式
- 意图:该模式的思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象
- 动机:在运行期建立和删除原型
- 适用场景
- 当一个系统应该独立于它的产品创建,构成和表示时
- 在运行时,指定需要实例化的类,例如动态载入
- 避免构建与产品的类层次结构相似的工厂类层次结构
- 当类的实例是仅有的一些不同状态组合之一的时候
典型应用:
-
细胞分裂
-
JAVA 中的 Object clone() 方法
-
优点:可以在运行时添加或删除产品,通过改变值指定新对象,通过改变结构指定新对象,减少子类的生成和使用,可以用类动态配置应用程序
-
缺点:1、当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。 3、逃避构造函数的约束
-
类图:
3.5 单例模式 Singleton
一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
- 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点
- 动机:一个全局使用的类频繁地创建与销毁
- 适用场景:当您想控制实例数目,节省系统资源的时候
典型应用:
- 要求生产唯一序列号。
- WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等
- 优点:对单个实例的受控制访问,比类操作更灵活,避免对资源的多重占用
- 缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化
- 类图:
4.结构型模式
结构性模式控制了应用程序较大部分之间的关系
4.1 适配器模式 Adapter
两个不兼容的接口之间的桥梁
- 意图:适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题,主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式
-
类的适配器模式:当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。
-
对象的适配器模式:当希望将一个对象转换成满足另一个新接口的对象时,可以创建一个Wrapper类,持有原类的一个实例,在Wrapper类的方法中,调用实例的方法就行。
-
接口的适配器模式:当不希望实现一个接口中所有的方法时,可以创建一个抽象类Wrapper,实现所有方法,我们写别的类的时候,继承抽象类即可
-
动机:将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的
-
适用场景:
- 系统需要使用现有的类,而此类的接口不符合系统的需要
- 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。
- 通过接口转换,将一个类插入另一个类系中
典型应用:
- 美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V
- JAVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,则要将以前系统的 Enumeration 接口转化为 Iterator 接口,这时就需要适配器模式
- 在 LINUX 上运行 WINDOWS 程序
- JAVA 中的 jdbc
- 优点:1.允许两个或多个不兼容的对象进行交互和通信,2.提高已有功能的重复使用性
- 缺点:1、过多地使用适配器,会让系统非常零乱,不易整体进行把握,2、由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类
- 类图:
4.2 装饰模式 Decorator
可以在不修改对象外观和功能的情况下添加或者删除对象功能,可以使用一种对客户端来说是透明的方法来修改对象的功能,也就是使用初始类的子类实例对初始对象进行授权
- 意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活
- 动机:一般为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀
- 适用场景
- 在不想增加很多子类的情况下扩展类
- 透明动态的为一个对象增加功能,而且还能动态撤销。(继承不能做到这一点,继承的功能是静态的,不能动态增删。)
- 无法通过静态子类化实现扩展时
典型应用:
- 不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体
- 优点:比静态继承具有更大的灵活性,避免了特征装载的类处于层次结构的过高级别,装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能
- 缺点:多层装饰比较复杂,产生过多相似的对象,不易排错
- 类图:
4.3 代理模式 Proxy
一个类代表另一个类的功能,创建具有现有对象的对象,以便向外界提供功能接口。如果已有的方法在使用的时候需要对原有的方法进行改进,此时有两种办法:
第一种:修改原有的方法来适应,但这样违反了“对扩展开放,对修改关闭”的原则
第二种:采用一个代理类调用原有的方法,且对产生的结果进行控制。这种方法就是代理模式。
- 意图:为其他对象提供一种代理以控制对这个对象的访问
- 动机:在直接访问对象时带来的问题,可以在访问此对象时加上一个对此对象的访问层
- 适用场景
- 想在访问一个类时做一些控制
典型应用:
- Windows 里面的快捷方式
- 买火车票不一定在火车站买,也可以去代售点
- 优点:功能划分的更加清晰,有助于后期维护,远程以隐藏对象位于不同的地址空间的事实,虚拟代理可以执行优化操作
- 缺点:1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂
- 关联性
- 和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
- 和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
- 类图:
4.4 外观模式 Facade
外观模式是为了解决类与类之间的依赖关系的,像spring一样,可以将类和类之间的关系配置到配置文件中,而外观模式就是将他们的关系放在一个Facade类中,降低了类类之间的耦合度,该模式中没有涉及到接口
- 意图:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
- 动机:降低访问复杂系统的内部子系统时的复杂度,简化客户端与之的接口
- 适用场景
- 为复杂的模块或子系统提供简单外界访问的模块
- 想要对子系统进行分层
- 在客户端和抽象的实现类中存在许多依赖关系统
典型应用:
-
去医院看病,可能要去挂号、门诊、划价、取药,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便。
-
JAVA 的三层开发模式
-
优点:减少系统相互依赖,提高子系统与其客户端之间的弱耦合度,对客户端屏蔽了子系统组件
-
缺点:不符合开闭原则,如果要改东西很麻烦,继承重写都不合适
-
类图:
4.5 桥接模式 Bridge
桥接模式就是把事物和其具体实现分开,使他们可以各自独立的变化。桥接的用意是:将抽象化与实现化解耦,使得二者可以独立变化,像我们常用的JDBC桥DriverManager一样,JDBC进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不用动,原因就是JDBC提供统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了。
- 意图:将抽象部分与实现部分分离,使它们都可以独立的变化
- 动机:在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活
- 适用场景
- 实现系统可能有多个角度分类,每一种角度都可能变化
- 一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系
- 抽象的实现被改动应该对客户端没有影响
- 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用
典型应用:
- 墙上的开关,可以看到的开关是抽象的,不用管里面具体怎么实现的
- 优点:1、抽象和实现的分离。 2、提高扩展能力。 3、对客户端隐藏了实现的细节
- 缺点:桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程
- 类图:
4.6 组合模式 Composite
又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次
使用场景:将多个对象组合在一起进行操作,常用于表示树形结构中,例如二叉树,数等。
- 意图:将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性
- 动机:它在树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦
- 适用场景
- 您想表示对象的部分-整体层次结构(树形结构)
- 您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象
- 结构可以具有任何级别的复杂性,而且是动态的
典型应用:
- 您算术表达式包括操作数、操作符和另一个操作数,其中,另一个操作符也可以是操作树、操作符和另一个操作数。
- 在 JAVA AWT 和 SWING 中,对于 Button 和 Checkbox 是树叶,Container 是树枝。
- 优点:1、高层模块调用简单,定义了由主要对象和复合对象组成的类层次结构。 2、节点自由增加,提供了结构的灵活性和可管理的接口
- 缺点:在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则
- 类图:
4.7 享元模式 Flyweight
可以通过共享对象减少系统中低等级的、详细的对象数目,以减少内存占用和提高性能。实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。FlyWeightFactory负责创建和管理享元单元,当一个客户端请求时,工厂需要检查当前对象池中是否有符合条件的对象,如果有,就返回已经存在的对象,如果没有,则创建一个新对象,FlyWeight是超类
- 意图:运用共享技术有效地支持大量细粒度的对象
- 动机:在有大量对象时,有可能会造成内存溢出,把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建
- 适用场景
- 系统中有大量对象
- 这些对象消耗大量内存
- 这些对象的状态大部分可以外部化
- 这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。
- 系统不依赖于这些对象身份,这些对象是不可分辨的
典型应用:
-
JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面
-
数据库的数据池
-
优点:大大减少对象的创建,降低系统的内存和存储设备,使效率提高
-
缺点:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱
-
类图:
5.行为型模式
行为性模式可以影响一个系统的状态和行为流,通过优化状态和行为流转换和修改的方式,可以简化,优化并且提高应用程序的可维护性
5.1 父类与子类
5.1.1 策略模式 strategy
需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类(可有可无,属于辅助类),提供辅助函数。策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各种算法做封装。因此,策略模式多用在算法决策系统中,外部用户只需要决定用哪个算法即可
- 意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换,且算法的变化不会影响到使用算法的客户
- 动机:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护
- 适用场景
- 一个系统有许多许多类,而区分它们的只是他们直接的行为
- 需要算法的不同变体
典型应用:
- 旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略
- JAVA AWT 中的 LayoutManager
- 优点:1、算法可以自由切换。 2、避免使用多重条件判断。 3、更容易扩展模型
- 缺点:1、策略类会增多。 2、所有策略类都需要对外暴露
- 类图:
5.1.2 模板方法模式 Template Method
一个抽象类中,有一个主方法,再定义1...n个方法,可以是抽象的,也可以是实际的方法,定义一个类,继承该抽象类,重写抽象方法,通过调用抽象类,实现对子类的调用,
- 意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤
- 动机:一些方法通用,却在每一个子类都重新写了这一方法
- 适用场景
- 想要一次实现算法的不变部分,而使用子类实现算法的可变行为
- 当子类间的通用行为需要分解,定位到通用类的时候,避免代码的重复的问题
典型应用:
- spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存
- 优点:1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
- 缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大
- 类图:
5.2 两个类之间
5.2.1 观察者模式Observer
- 意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
- 动机:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作
- 适用场景
- 对一个对象的修改涉及对其它对象的悠,而且不知道有多少对象需要进行相应修改
- 对象应该能够在不用假设对象标识的前提下通知其它对象
典型应用:
- 拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价
- 优点:抽象了主体与observer之间的耦合关系,支持广播方式的通信
- 缺点:
- 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化
- 类图:
5.2.2 迭代器模式 Iterator
- 意图:提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示
- 动机:不同的方式来遍历整个整合对象
- 适用场景
- 访问一个聚合对象的内容而无须暴露它的内部表示
- 需要为聚合对象提供多种遍历方式
- 为遍历不同的聚合结构提供一个统一的接口
典型应用:
- JAVA 中的 iterator
- 优点:支持集合的不同遍历,简化了集合的接口
- 缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类
- 类图:
5.2.3 责任链模式 Chain of Responsibility
有多个对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这条链上传递,直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求,所以,责任链模式可以实现,在隐瞒客户端的情况下,对系统进行动态的调整
- 意图:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止
- 动机:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了
- 适用场景
- 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定
- 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求
- 可动态指定能够处理请求的对象集
典型应用:
- JS 中的事件冒泡
- jsp servlet 的 Filter
- 优点:降低耦合度,增加向对象指定责任的灵活性
- 缺点: 1、不能保证请求一定被接收。 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3、可能不容易观察运行时的特征,有碍于除错
- 类图:
5.2.4 命令模式 Command
命令模式很好理解,举个例子,司令员下令让士兵去干件事情,从整个事情的角度来考虑,司令员的作用是,发出口令,口令经过传递,传到了士兵耳朵里,士兵去执行。这个过程好在,三者相互解耦,任何一方都不用去依赖其他人,只需要做好自己的事儿就行,司令员要的是结果,不会去关注到底士兵是怎么实现的。
- 意图:将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化
- 动机:在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适
- 适用场景
- 想要通过要执行的动作来参数化对象
- 要在不同的时间指定,排序以及执行请求
- 必须支持undo,日志记录或事务
典型应用:
- struts 1 中的 action 核心控制器 ActionServlet 只有一个,相当于 Invoker,而模型层的类会随着不同的应用有不同的模型类,相当于具体的 Command
- 优点:1.将调用操作的对象与知道如何完成该操作的对象相分离,2.容易添加新的命令
- 缺点:使用命令模式可能会导致某些系统有过多的具体命令类
- 类图:
5.3 类的状态
5.3.1 备忘录模式 Memento
主要目的是保存一个对象的某个状态,以便在适当的时候恢复对象
- 意图:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态
- 动机:将对象恢复到原先保存的状态
- 适用场景
- 必须保存对象状态的快照,方便以后恢复状态
- 使用直接接口来获得状态可能会公开对象的实现细节,从而破坏对象的封装性
典型应用:
- Windows 里的 ctri + z
- 数据库的事务管理
- 优点:保持封装的完整,简化了返回到初始状态所需的操作
- 缺点:消耗资源
- 类图:
5.3.2 状态模式 State
- 意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类
- 动机:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为
- 适用场景
- 行为随状态改变而改变的场景
- 条件、分支语句的代替者
典型应用:
- 1111
- 优点:定位指定状态的行为,并且针对不同状态来划分行为,使状态转换显示进行
- 缺点:状态模式的结构与实现都较为复杂,状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码
- 类图:
5.4 通过中间类
5.4.1 访问者模式 Visitor
访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。
简单来说,访问者模式就是一种分离对象数据结构与行为的方法,通过这种分离,可达到为一个被访问者动态添加新的操作而无需做其它的修改的效果。简单关系图:
- 意图:主要将数据结构与数据操作分离
- 动机:稳定的数据结构和易变的操作耦合问题
- 适用场景
- 对象结构包含许多不同接口的对象类,并且想要对之些依赖于具体类的对象进行操作
- 定义对象的结构的类很少被修改,但想要在些结构上定义新的操作
典型应用:
- 1111
- 优点:容易添加新操作,集口相关操作并且排除不相关的操作
- 缺点:增加新的数据结构很困难。
- 类图:
5.4.2 中介者模式 Mediator
中介者模式也是用来降低类类之间的耦合的,因为如果类类之间有依赖关系的话,不利于功能的拓展和维护,因为只要修改一个对象,其它关联的对象都得进行修改。如果使用中介者模式,只需关心和Mediator类的关系,具体类类之间的关系及调度交给Mediator就行,这有点像spring容器的作用。
- 意图:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
- 动机:对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理
- 适用场景
- 系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象
- 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类
典型应用:
- MVC 框架,其中C(控制器)就是 M(模型)和 V(视图)的中介者
- 优点:去除对象间的影响,简化了对象间的协议,集口化了控制,由于不需要直接互传消息,单个组件简单,通用,容易处理
- 缺点:中介者会庞大,变得复杂难以维护
- 类图:
5.4.3 解释器模式 Interpreter
- 意图:给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子
- 动机:对于一些固定文法构建一个解释句子的解释器
- 适用场景
- 语言的语法比较简单
- 效率并不是最主要的问题
典型应用:
- 编译器、运算表达式计算
- 优点:1、可扩展性比较好,灵活。 2、增加了新的解释表达式的方式。 3、易于实现简单文法
- 缺点:1、可利用场景比较少。 2、对于复杂的文法比较难维护。 3、解释器模式会引起类膨胀。 4、解释器模式采用递归调用方法
- 类图: