设计模式
Posted yishuo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式相关的知识,希望对你有一定的参考价值。
六大基本原则:
开闭原则: 是对修改关闭,对新增开放,开闭原则是面向对象对程序设计的终极目标,
- 对软件测试的影响:如果遵守开闭原则,软件的测试只要对新增的代码进行测试,原有的代码不用在进行测试仍能继续运行。
- 可以提交代码的复用性:粒子越小,复用性越大。根据原子和抽象编程可以提高代码的复用性
- 提高的软件的维护性:遵守了开闭原则,稳定性和延续性提高,从而便于提高扩展性和维护。
里氏替换原则:是说有关继承的一些原则,什么时候应该继承,什么时候不应该继承,以及其中的一些原理。是继承和复用的原理,反应了父类和子类之间的关系。是对开闭原则的补充。
里氏替换原则作用:
- 是实现开闭原则的重要方法之一。
- 克服了继承重写父类造成的可复用性变差的缺点
- 它是动作正确的保证,基类的扩张不会给已有的系统引入新的错误,降低了代码出错的可能性
里氏替换原则通俗来讲:子类可以扩张 父类的功能,但是不能改变父类原有的功能。
依赖倒置原则:核心的思想是要面向接口编程,不要面向实现编程。通过面向接口编程降低类间的耦合性
依赖倒置原则是实现开闭原则的重要途径之一,它降低了客户与模块之间耦合。
由于在软件设计中,细节是多变的。 而抽象层则是稳定的, 因此以抽象为基础搭建起来的框架要比细节为基础搭建起来的框架稳定的多。这的的抽象是指接口或者是抽象类, 而细节是指具体的实现类。
依赖倒置原则的作用:
- 可以降低类之间的耦合性
- 可以提高系统的稳定性
- 可以减少并行开发引起的风险
- 可以提高代码的可阅读性和可维护性
依赖倒置原则的实现方法:
- 每个类尽量提供接口或者是抽象类,或者是两者都有
- 变量的声明类型尽量是接口或者是抽象类。
- 任何类都不应该从具体的类派生
- 使用继承时尽量遵守里氏替换原则
单一职责原则:这里是指类变化的原因,单一职责的原则是规定一个类有且仅有一个引起它变化的原因,否则类被拆分。
单一职责原则的优点和缺点:
单一职责不应该承太多职责,如果承担了太多的职责 缺点:
- 一个职责的变化可能会削弱或者是抑制这个类实现的其他职责
- 当客户端需要对该对象的某一个职责时,不的不将其他不需要的职责全部包含起来,从而造成代码的冗余和代码的浪费。
单一职责的核心就是控制类的粒度大小,将对象解耦提高其内聚性 优点:
- 降低了累的复杂度,一个类只负责一项职责,这比那一个类负责多项职责的简单的多。
- 提高可阅读性,复杂性降低,自然其阅读性就提高了。
- 提高了系统的可维护性,可阅读性提高了,自然维护就好维护了。
- 变更引起的风险降低,变更是必然的,如果单一职责遵循的好,当修改一个功能的时候,可以显著的降低对其他的功能的影响。
单一职责的原则的实现:
单一职责原则是最简单但是又是最艰难的。需要设计人员发现类的不同职责, 需要将其分离出来。在封装到不同的方法或者是模块中,而发现类有多重职责的时候,需要设计人员有较强的分析能力设计能力和 相关的重构的经验。
接口隔离原则:(Interface Segregation Principle,ISP) 要求程序员将臃肿庞大的接口拆分成更小更细的的接口, 让客户对接口更感兴趣的方法。
接口隔离原则和单一职责都是为了提高类的内聚性,降低它们之间的耦合性,体现了封装的思想。但是两者有不同点:
- 单一职责原则注重的是职责,接口隔离原则注重的是对接口依赖的隔离
- 单一职责原则主要的约束类,它主要针对程序中实现的细节,但是接口隔离原则约束的是接口,主要针对抽象和程序整体框架的构建。
接口隔离原则的优点:
- 将臃肿庞大的接口分解成多个粒度小的接口,可以预防外来变更的扩散,提高系统的灵活和可维护性。
- 接口隔离提高了系统的内聚性,降低了系统的耦合性。
- 如果接口的粒度大小定义合理,能够保证系统的稳定性;但是定义过小,就会造成接口数据过多,造成了设计的复杂化。如果定义太大,灵活性降低,无法提供定制服务。给整体项目带来无法预料的风险。
- 使用多个专门的接口还能够体现对象的层次,因为可以通过接口继承,实现对总接口的定义。
- 能够减少项目工程中的代码冗余,过大的接口里面通常放置许多不用的方法,当实现这个接口的时候,被迫设计冗余的代码。
接口隔离的原则实现方法:
- 接口尽量小,但是要有个限度。一个接口只服务于一个子模块或者业务逻辑。
- 为了依赖接口定制服务,只提供使用者需要的方法,屏蔽不需要的方法,
- 了解环境,拒绝盲从,每个项目或产品都有选定的环境因素,环境不同,接口拆分的标准就不同深入了解业务逻辑。
- 提高内聚,减少对外交互,是接口用最少的方法完成最多的事情,
迪米特法则: 又叫做最少知识原则。目的是降低类之间的耦合度,提高模块的相对独立性。
迪米特法则的优点:
迪米特法则要求显示软件实体之间的通信的宽度和深度。正确使用迪米特法则有以下两个优点:
- 降低了类之间的耦合度,提高了模块的相对独立性,
- 由于亲和度降低,从而提高了类的可复用率和系统的扩展性
迪米特法则的实现方法:
- 在类的划分上,应该创建弱耦合的类,类与类之间的耦合越弱,就越有利实现可复用的目标。
- 在类的结构上设计上,尽量降低类的成员的访问权限,
- 在类的设计上,有先考虑将一个类设置成不变的类,
- 在对其他了的引用上, 将引用其他的对象的次数降到最低
- 不暴露类的属性成员,而应该提供相应的访问器(get 和 set 方法)。
- 谨慎使用序列化功能。
合成复用原则的定义:又叫组合或者是聚合复用原则。要求软件在复用时,尽量使用组合或者是聚合等关联关系来实现。其次才考虑使用继承关系来实现。
如果要是使用继承关系,则必须严格遵循里氏替换原则。合成复用原则和里氏替换原则相辅相成,两个都是开闭原则的具体实现规范。
合成复用原则的重要性:通常类的复用分两种继承复用和合成复用。继承复用虽然有简单复用的有点但是也有缺点
- 继承复用破坏了累的封装性,因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所有这种复用又称为“白箱”复用。
- 子类与弗雷德耦合度高,父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
- 它限制了复用的灵活性,从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。
采用组合或聚合复用时,可以将已有的对象纳入新的对象中,使之成为新对象的一部分,新对象可以调用已有对象功能,有以下优点:
- 它维持了类的封装性,因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱” 复用。
- 新旧之间的耦合度低,这种复用所需的依赖较少,新对象存取成分对应的唯一方法就是通过成分对象的接口。
- 复用的灵活性高。这种复用可以在运行时动态进行,新队形可以动态地引用与成分队形类型相同的对象
合成复用原则的实现方法:合成复用原则是通过将已有的对象纳入新的对象中,作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用。
总结前面所述一共有7种设计原则,它们分别为 开闭原则 里氏替换原则 依赖倒置原则 单一职责原则 接口隔离原则 迪米特法则 和 合成复用原则。
这几种设计原则是软件设计模式必须尽量遵循的原则,各种原则要求的侧重点不同,其中,
开闭原则是总纲,它告诉我们要对扩展开放,对修改关闭;
里氏替换原则告诉我们不要破坏继承体系;
依赖倒置原则告诉我们要面向接口编程;
单一职责原则告诉我们实现类要职责单一;
接口隔离原则告诉我们在设计接口的时候要精简单一;
迪米特法则告诉我们要降低耦合度;
合成复用原则告诉我们要优先使用组合或者是聚合关系复用,少用继承关系复用。
创建型模式:主要的特点 将对象的创建与使用分离。
创建型模式分几种:
- 单例模式:某个类智能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其扩展是有限多例模式。
- 原型模式:将一个对象作为原型,通过对其进行复制而克隆出对个和原型类似的新实例。
- 工厂方法模式:定义一个创建产品的接口, 有子类决定生产什么产品。
- 抽象工厂模式:提供一个创建产品族的接口,其每个子类可以生产以系列相关的产品。
- 建造者模式:将一个复杂的对象分解成多个相对简单的部分,然后根据不同需求分别创建,,最后构建成该复杂的对象
这5种创建型模式,出了工厂方法模式属于类创建型模式,其他的全部属于对象创建型模式,
单例模式:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。
在计算机的系统中,还有windows的回收站,操作系统中的文件系统, 多线程的线程池,显卡驱动程序对象,打印机的后台处理服务,应用程序的日志对象,数据库的连接池,网站的计数器,web应用的配置对象,应用程序中的对话框,系统中的缓存等常常被设计成单例。
单例模式的特点:
- 单例类只有一个实例对象。
- 改单例对象必须由单例类 自行创建。
- 单例类对外提供一个访问该单例的全部访问点
单例模式的结构与实现:
单例模式是设计模式中最简单的模式之一, 通常,普通类的构造函数是公有的, 外部类可以通过new 后遭函数() 来生成多个实例,但是如果将类的构造函数设为私有的外部类就无法调用构造函数,也就无法生成多个实例,这时,类自身必须定义一个静态私有的实例, 并向外提供一个静态的公有函数用于出餐间或者获取改静态私有的实例。
原型模式:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或者是相似的对象。
原型模式的结构和实现:
原型模式主要包含以下的角色:
- 抽象原型类:规定了具体原型对象必须实现的接口。
- 具体原型类:实现抽象原型类的clone()方法,它是被可复制的对象。
- 访问类:使用具体原型类的clone()方法来复制性的 对象。
原型模式的实现:
原型模式的克隆分为浅克隆和深克隆,java中object类提供的是浅克隆clone()方法,具体提供原型类只要实现Cloneable 接口就可以实现对象的浅克隆 代码如下
1 //具体原型类
2 class Realizetype implements Cloneable
3 {
4 Realizetype()
5 {
6 System.out.println("具体原型创建成功!");
7 }
8 public Object clone() throws CloneNotSupportedException
9 {
10 System.out.println("具体原型复制成功!");
11 return (Realizetype)super.clone();
12 }
13 }
14 //原型模式的测试类
15 public class PrototypeTest
16 {
17 public static void main(String[] args)throws CloneNotSupportedException
18 {
19 Realizetype obj1=new Realizetype();
20 Realizetype obj2=(Realizetype)obj1.clone();
21 System.out.println("obj1==obj2?"+(obj1==obj2));
22 }
23 }
程序的运行结果如下:
具体原型创建成功! 具体原型复制成功! obj1==obj2?false
工厂方法模式:定义一个创建产品对象的工厂的接口,将产品对象的实际创建工作推迟工作推迟到具体的子工厂类当中。这满足了创建型模式中所要求的“创建于使用相分离” 的特点。
工厂方法模式的主要优点有:
- 用户只需要知道具体工厂的名称就可的到所要的产品,无需知道产品的具体创建过程。
- 在系统增加新的产品时只需要添加具体的产品类和队形的具体工厂类,无需对原工厂进行任何修改,满足了开闭原则。
工厂方法模式的缺点是: 每增加一个产品就要增加一个具体的产品类和一个对应的具体工厂类, 这增加了系统的复杂度。
工厂方法模式结构与实现: 是由抽象工厂,具体工厂, 抽象产品和具体的产品等4个要素构成。
模式的结构:
- 抽象工厂:提供了创建产品的接口,调用这通过它的访问具体工厂的工厂方法来创建产品。
- 具体工厂:主要是实现抽象工厂中的抽象方法,完成具体的产品的创建。
- 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品:实现了抽象产品角色所定义的接口, 由具体的工厂来创建,它同具体工厂之间一一对应
抽象工厂:一种为访问类提供的一个创建一组相关或相互依赖对象的接口,且反问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可以生产多个等级的产品。
使用抽象工厂模式一般要满足一下条件:
- 系统中有多个产品族。每个具体工厂创建同一组但属于不同等级结构的产品。
- 系统一次可能消费其中某一族产品。即同族的产品一起使用。
抽象工厂模式出了具有工厂方法模式的优点外。 其他的主要的优点:
- 可以在类的内部对产品族中相关联的多等级产品共同管理。而不必专门引入多个新的类进行管理。
- 当增加一个新的产品族时不需要修改源代码,满足开闭原则。
其缺点是:当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
建造者模式:是指将一个复杂的对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。
它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建成,它将变与不变相分离,即产品的组成部分是不变的, 但每一部分 是可以灵活选择的
建造者模式的优点:
- 各个具体的建造者相互独立。有利于系统的扩展。
- 客户端不必知道产品内部组成的细节,便于控制细节风险。
建造者模式的缺点:
- 产品的组成部分必须相同,这限制了其使用范围。
- 如果产品的内部变化复杂,该模式会增加很多的建造者类。
建造者模式和工厂模式的关注点不同:建造者模式注重零件部件的组装过程,而工厂模式更注重零部件的创建过程,但两者可以结合使用。
建造者模式的结构:
建造者(Builder)模式由产品、抽象建造者、具体建造者、指挥者等 4 个要素构成,现在我们来分析其基本结构和实现方法。
建造者(Builder)模式的主要角色如下。
- 产品角色:它包含了多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
- 抽象建造者:它是一个包含创建产品各个子部件的轴向方法的接口,通常还包含一个返回负载产品的方法getResult();
- 具体建造者:实现Builder接口,完成复杂产品各个部件的具体创建的方法。
- 指挥者:它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体的产品的信息。
结构型模式:它分为类结构型模式和对象结构型模式。前者采用继承机制来组织接口和类。后者采用组合或聚合来组合对象。
由于组合关系或聚合关系比继承关系耦合度低,满足 “合成复用的原则” 。所以对象结构型模式比类结构型模式具有更大的灵活性。
结构型模式分为7种:
- 代理模式:为某对象提供一种代理以控制对该对象的访问,即客户端通过代理间接地仿问该对象,从而限制,增强或修改该对象的一些特性。
- 适配器模式:将一个类的接口狗转换成客户希望的另外一个接口,是的原本由于接口不兼容而不能一起工作的那些类能一起工作。
- 桥接模式:将 抽象与现实分离,是他们可以独立变化,他是用组合关系代替继承关系来实现,从而降低了抽象和现实这两个可变未读的耦合度。
- 装饰模式:动态地给对象增加一些职责,即增加其额外的功能。
- 外观模式:为多个复杂的子系统提供一个一致的即可购, 使这些子系统更加容易被访问。
- 享元模式:运用共享技术来有效的支持大量细粒度度的对象复用。
- 组合模式:将对象这成树状层次结构。使用户对单个对象和组合对象具有一致的访问性。
这7种结构型模式,除了适配器模式分为类结构型模式和对象结构型模式两种,其他全部属于对象结构型模式,
代理模式的定义与特点:
由于某些原因需要给某对象提供一个代理以控制对象的访问,这时,访问对象不适合或者不能直接访问目标对象,代理对象作为访问对象和目标对象之间的中介。
代理模式主要的优点:
- 代理模式在客户端与目标对象之间起到了一个中介作用和保护目标对象的作用。
- 代理对象可以扩展目标对象的功能。
- 代理模式能将客户单与目标对象分离,在一定程度上降低了系统的耦合度。
其主要的缺点:
- 在客户端和目标对象之间增加了一个代理对象,会造成请求处理速度慢。
- 增加了系统的复杂度。
代理模式的结构与现实:
代理模式的结构比较简单,主要是通过定义一个集成抽象主题的 代理来包含真实的主题,从而实现了对真实主题的访问。
模式的结构: 代理模式的主要角色如下
- 抽象主题类:通过结构或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问,控制或扩张真实主题的功能。
适配器模式: 适配器模式分类结构型模式和对象结构型模式。前者类之间的耦合度比后者高。且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
该模式的主要的优点:
- 客户端通过适配器可以透明的调用目标接口。
- 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
- 将目标类和适配者类解耦,解决了 目标类和适配器类接口不一致的问题。
其缺点:对类适配器来说,更换适配器的实现过程比较复杂。
适配器的结构和实现:
类适配器模式可采用多重继承方式来实现,如C++ 可定义一个适配器类来同时继承当前系统的业务接口和现有组件库中已经存在的组件接口;java 不支持多继承,但可以定义一个适配器类来实现当前系统的业务接口,同事有继承现有库中已经存在组件。
对象适配器模式可以采用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务而接口,现在来介绍他们的基本结构。
适配器模式的结构:
- 目标接口:当前系统业务所期待的接口,它是可以抽象类或接口,
- 适配器类:它是被访问和适配的现存组件库中的组件接口。
- 适配器类:它是一个转换器,通过集成或适配器的对象,把适配器接口转换成目标接口,让客户按目标接口的格式访问适配者。
桥接模式:将抽象与实现分离,使它们可以独立变化, 它是用组合关系代替继承关系来实现, 从而降低了抽象和实现这两个可变未读的耦合度。
桥接模式的优点:
- 由于抽象与实现分离,所以扩展能力强。
- 其实现细节对客户透明。
其缺点:由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,这增加了系统的理解与设计难度。
桥接模式的结构与实现: 可以将抽象化部分与实现化分分开,取消二者的继承关系,改用组合关系。
桥接模式的结构:
- 抽象化角色:定义抽象类,并包含一个对实现化对象的引用,
- 扩展抽象化角色:是抽象化角色的子类。实现父类中的业务而方法。并通过组合关系调用实现化角色中的业务方法。
- 实现化角色:定义实现化角色的接口,公扩展抽象化角色调用。
- 具体实现化角色:给出实现化角色接口的具体实现。
装饰模式:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(增加其额外的功能)的模式,它属于对象结构型模式。
装饰模式的主要优点:
- 采用装饰模式扩展对象的功能比采用继承方法更加灵活。
- 可以设计出多个不同的具体装饰类,创造出多个不同行为的组合。
其缺点是:装饰模式增加了许多子类,如果过度使用会使程序变得很复杂。
装饰模式结构:
- 抽象构件角色:定义一个抽象接口以规范准备接收附加责任对象。
- 具体构件角色:实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
- 具体装饰角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
外观模式: 是一个通过为多个复杂的子系统提供一个一致的接口, 而使这些子系统更加容易被访问的模式,该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性,
外观模式是 “迪米特法则” 的典型应用,主要的优点:
- 降低了子系统与客户端之间的耦合度,使的子系统的变化不会影响调用它的客户类。
- 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
- 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他子系统,也不会影响外观对象。
外观模式的缺点:
- 不能很好的限制客户使用子系统类,
- 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
外观模式的结构与实现:
外观模式的机构比较简单,主要定义一个高层接口,它包含了对各个子系统的引用, 客户端可以通过它的访问各个子系统的功能, 现在来分析其基本结构和实现方法。
外观模式的结构: 主要包含以下的角色
- 外观角色:为多个子系统对外提供一个共同的接口。
- 子系统角色:实现系统的部分功能,客户可以通过外观角色访问它。
- 客户角色:通过一个外观角色访问各个子系统的功能。
享元模式:运用共享技术来有效地支持大量细粒度对象的复用,它通过共享已经存在的又像来大幅度减少需要创建的对象数量,避免大量相似类的开销,从而提高系统资源的利用率。
享元模式的优点:相同对象只要保存一份,这降低了系统中对象的数量。从而降低了系统中细粒度对象给内存带来的压力。
享元模式的缺点:
- 为了使对象可以共享, 需要将一些不能共享的状态外部化。这将增加程序的复杂性。
- 读取享元模式的外部状态会使得运行时间稍微变长。
享元模式的结构与实现: 分为两种状态
- 内部转态,即不会随着环境的改变而改变的可共享部分。
- 外部状态,指随环境改变而改变的不可以共享的部分,享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化,
享元模式的结构:
- 抽象享元角色:是所有的具体享元类的基类,为具体享元规范需要实现的公共接口狗,非享元的外部状态以参数的形式通过方法传入。
- 具体享元角色:实现抽象享元角色所规定的接口,
- 非享元角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中,
- 享元工厂角色:负责创建和管理享元角色,当客户对象请求一个享元对象时,享元工厂检查系统中是否存在符合要求的享元对象,如果存在则提供给客户,如果不存在的话。则创建一个新的享元对象。
组合模式:又叫部分-整体模式,它是一种将对象组合成树状的层次结构的模式,用来表示 “部分-整体” 的关系,使用户对单个对象和组合对象具有一致的访问性。
组合模式的主要优点:
- 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码
- 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足 “开闭原则” 。
组合模式的缺点:
- 设计比较复杂,客户端需要花更多时间清类之间的层次关系
- 不容易限制容器中的构件。
- 不容易用继承的方法来增加构件的新功能。
组合模式的结构与实现: 模式的结构
- 抽象构件角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为,在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类接口,管理工作由树枝构件完成。
- 树叶构件角色:是组合中的叶节点对象,它没有子节点,,用于实现抽象构件角色中,声明的公共接口。
- 树枝构件角色:是组合的分支节点对象,它有子节点,它实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,
组合模式分为 透明式的组合模式 和安全式的组合模式。
行为型模式:描述程序在运行时复杂的流程的控制,即描述多个类或对象之间怎么样相互协作共同完成单个对象都无法单独完成任务,它涉及算法与对象间的职责分配。
行为模式分类:行为模式 和 对象模式。前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系和聚合关系比继承关系耦合度低,满足 “ 合成复用原则” ,所有对象行为模式比类行为模式具有更大的灵活性。
行为型模式 是设计模式 最为庞大的一类。包含以下11种模式:
- 模板方法模式: 定义一个操作中的算法骨架,将算法的一些步骤延迟到子类中,使得子类在可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
- 策略模式:定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的改变不会影响使用算法的客户。
- 命令模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
- 职责链模式:请求从链中的一个对象传到下一个对象,知道请求被响应为止,通过这种方式出对象之间的耦合,
- 状态模式:允许一个对象在其内部状态发生改变时改变其行为能力。
- 观察者模式:对个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
- 中介模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
- 迭代器模式:提供一种方法来顺序访问巨虎对象中的一系列数据,而不暴露聚合对象的内部表示,
- 访问者模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象的访问。
- 备忘录模式:在不破坏封装性的前提下, 获取并保存一个对象的内部状态,以便以后恢复它。
- 解释器模式:提供如何定义语言的文法,一级对语言橘子的解释方法,即解释器。
以上11种行为型模式,除了模板方法模式 和 解释器模式 是类行为型模式,其他的全部属于对象行为型模式。
模板方法模式: 定义一个操作总的算法骨架,而将算法的一些步骤延迟到子类中,是的子类可以不改变该算法结构的情况下重定义改算法的某些特定步骤,它是一种类行为型模式。
该模式的优点:
- 它封装了不变部分,扩展可变部分,它吧认为是不变部分得算法封装到分类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
- 它在父类中提取了公共的部分代码,便于代码复用。
- 部分方法是由子类实现的,因此子类可以铜鼓扩展方法增加相应的功能,符合开闭原则。
该模式的缺点:
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
模式的结构与实现:模板方法模式需要注意抽象类与具体子类之间的协作,它用到了虚函数的多态性技术以及 “不用调用我,让我来调用你” 的反向控制技术,
模板模式结构,包含以下角色:
- 抽象类:负责给出一个算法的轮廓和骨架,它由一个模板方法和若干个基本方法构成,如下:
- 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
- 基本方法:是整个方法中一个步骤,包含几种类型:
- 抽象方法:在抽象类中申明,由具体子类实现。
- 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
- 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
- 具体子类:实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
策略模式:该模式定义一个一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响的使用算法的客户,策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象这些算法进行管理。
策略模式的优点:
- 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。
- 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
- 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的
- 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法,
- 策略模式把算法的使用放在环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
其缺点:
- 客户端必须理解所有策略算法的区别。以便适时选择恰当的算法类。
- 策略模式造成了很多的策略类。
模式的结构:
- 抽象策略类:定义一个工公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口不同的算法,一般使用接口或抽象类 实现。
- 具体策略类:实现了抽象策略定义接口,提供具体的算法实现。
- 环境类:持有一个策略类的引用,最终给客户端调用。
命令模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开,这样两者之间通过命令对象进行沟通,这样方便将命令对选哪个进行储存,传递,调用,增加与管理。
命令模式主要优点:
- 降低了系统的耦合度,命令模式能将调用操作的对象与实现该操作的对象解耦。
- 增加或删除命令非常方便,采用命令模式增加与删除命令不会影响其他类。 他满足 "开闭原则" ,对扩展比较灵活。
- 可以实现宏命令,命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
- 方便实现 Undo 和 Redo 操作,命令模式可以与后面介绍的 备忘录模式 结合,实现命令的撤销与恢复。
其缺点:可能产生大量具体命令类,因为计对每一个具体操作都需要设计一具体命令类,这将增加系统的复杂性。
命令模式的结构:
- 抽象命令类角色:声明执行命令的接口,拥有执行命令的抽象方法 execute(),
- 具体命令角色:是抽象命令类具体 实现类,它拥有接收者对象,并通过调用接受者的功能来完成命令要执行的操作。
- 实现这/接收者角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。
- 调用者/请求者角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,,它不直接访问接收者。
责任链模式:为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链,当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
注意:责任链模式也叫职责链模式。
责任链模式是一种对象行为型模式,主要的优点如下:
- 降低了对象之间的耦合度,该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收 者也无须拥有对方的明确信息。
- 增强了系统的可扩展性,可以根据需要增加新的请求处理类,满足了开闭原则。
- 增强 了给对象指派职责的灵活性,当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
- 责任链简化了对象之间的连接,每个对象只需要保持一个指向其后继者的引用,不需要保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
- 责任分担,每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
其缺点:
- 不能保证每个请求一定被处理,由于一个请求灭有明确的接收者。所有不能保证它一定会被处理, 该亲戚去可能一直到链的末端都得不到处理。
- 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
责任链模式结构:
- 抽象处理者角色:定义一个处理请求的接口,包含抽象对象处理方法和一个后继连接。
- 具体处理者角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理。否则将该请求转给它的后继者。
- 客户类角色:创建处理链,并向链头的具体处理者对象提交请求,它不关系处理细节和请求的传递过程。
- 状态模式的使用必然会增加系统的类与对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
- 环境角色:也称为上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将于状态相关的操作委托给当前状态对象来处理。
- 抽象状态角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
- 具体状态角色:实现抽象状态所对应的行为。
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系,
- 目标与观察者之间建立了一套触发机制,
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
- 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
- 抽象主题角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加,删除观察者对象的方法,以及通知所有观察者的抽象方法。
- 具体主题角色:也叫具体目标类,它实现抽象目标中通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
- 抽象观察者角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
- 具体观察者角色:实现抽象观察者中定义的抽象方法,以便在的到目标的更改通知时更新自身的状态。
- 降低了对象之间的耦合性,使得对象易于独立地被复活,
- 将对象间的一对多关联转变为一对一关联,提高系统的灵活性,使得系统易于维护和扩展。
- 抽象中介者角色:他是中介者的接口,提供了同事对象注册于转发同事对象信息的抽象方法。
- 具体中介者角色:实现中介者接口,定义一个list来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
- 抽象同事类角色:定义同事累的接口, 保存中介者对象,提供同事对象交互的抽象方法,实现就爱你所有相互影响的同事类的公共功能,
- 具体同事类角色:是抽象同事类的实现,当需要与其他同事对象交互时,由中介者对象负责后续的交互,
- 访问一个聚合对象的内容而无须暴露它的内部表示,
- 遍历任务交由迭代器完成,这简化了聚合类,
- 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历,
- 增加新的聚合类和迭代器类都很方便,无须修改原有代码。
- 封装性良好,为遍历不同的聚合结构提供一个统一的接口。
- 抽象聚合角色:定义存储,添加,删除聚合对象以及创建迭代器对象的接口,
- 具体聚合角色:实现抽象聚合类,返回一个具体迭代器的实例,
- 抽象迭代器角色:定义访问和遍历聚合元素的接口,通常包含 hasNext() first() next() 等方法,
- 具体迭代器角色:实现抽象迭代器接口中所定义的方法,完成聚合对象的遍历,记录遍历的当前位置,
- 扩展性好,能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能,
- 复用性好,可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
- 灵活性好,访问者模式将数据结构与作用于结构上的操作解耦, 使得操作集合可相对自由地演化而不影响系统的数据结构,
- 符合单一职责原则,访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一,
- 增加新的元素类很困难,在访问这模式中,每增加一个行的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了 开闭原则。
- 破坏了封装,访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
- 违反了依赖倒置原则,访问者模式依赖了具体类,而没有依赖抽象类。
- 抽象访问者角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作visit() 该操作中的参数类型标识了被访问的具体元素,
- 具体访问者角色:实现抽象访问者角色中声明的各个访问操作,确定访问者一个元素时该做什么。
- 抽象元素角色:声明一个包含接收操作accept() 的接口,被接收的访问者对昂作为 accept() 方法的参数,
- 具体元素角色:实现抽象元素角色提供的 accept() 操作,其方法体通常是visitor.visit(this) 另外具体元素中可能还包含本身业务逻辑的相关操作,
- 对象结构角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由list ,set map 等聚合类实现,
- 提供了一个种可以恢复状态的机制,当用户需要时能够比较方便的将数据恢复到某个历史的状态,
- 实现了内部状态的封装,出了创建它的发起人之外,其他对象都不能够访问这些状态信息。
- 简化了发起人类,发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则,
- 发起人角色:记录当前时刻内部状态信息,提供备忘录和回复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息,
- 备忘录角色:负责存储发起人的内部状态。在需要的时候提供这些内部状态给发起人,
- 管理者角色;对备忘录进行管理,提供保存于获取备忘录的功能,但其不能堆备忘录的内容进行访问与修改,
- 扩展性好,由于在解释器模式中使用类来标示语言的文法规则,因此可以通过继承等机制来改变或扩展文法,
- 容易实现,在语法树中的每个表达式节点类都是 相似的,所以实现其文法较为容易。
- 执行效率低,解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
- 会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时, 类的个数将急剧增加,导致系统难以管理与维护。
- 可应用的场景比较少,在软件开发中,需要定义语言的文法的应用实例非常少,所以这种模式很少被使用到。
- 抽象表达式角色:定义解释器的接口,预定解释器的解释操作,主要包含解释方法 interpret()
- 终结符表达式角色:是抽象表达式的子类。用来实现文法中与终结符相关的操作,文法中每一个终结符都有一个决堤终结表达式与之相对应。
- 非终结符表达式角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
- 环境角色:通常包含各个解释器的数据或者是公共的功能,一般用来传递所有解释器共享的数据,后面的解释器可以从这里获取这些数值。
- 客户端:主要的任务是将需要的分析句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释的解释方法,
以上是关于设计模式的主要内容,如果未能解决你的问题,请参考以下文章