设计模式
六大原则
- 开闭原则 (Open-Closed-Principle)
- 核心:一个软件实体应当对拓展开放,对修改关闭。即:软件实体应尽量在不修改原有代码的情况下进行拓展
- 里氏代换原则(Liskow-Substitution-Principle)
- 核心:所有引用基类(父类)的地方,都必须能透明地使用其子类的对象。
- 依赖倒转原则(Dependency-Inversion-Principle)
- 核心:抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而非针对实现编程。
- 单一职责原则(Single-Responsibility-Principle)
- 核心:一个类只负责一个功能领域中相应的职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
- 接口隔离原则(Interface-Segregation-Principle)
- 核心:使用多个专门的接口,而不使用单一的总接口。即 客户端不应该依赖于那些它不需要的接口。
- 迪米特法则(Law-Of-Demeter)
模式动机与定义
- 模式动机
- 只需要知道名字就可以得到相应的类
- 模式定义
- 简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。
- 在简单工厂模式中,可以根据参数的不同返回不同类的实例。
- 简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
- 模式动机
模式结构与分析
- 模式结构
- 简单工厂模式包含如下角色:
- Factory:工厂角色
- Product:抽象产品角色
- ConcreteProduct:具体产品角色
- 简单工厂模式包含如下角色:
- 模式分析
- 将对象的创建和对象本身业务处理分离可以降低系统的耦合度,使得两者修改起来都相对容易。
- 在调用工厂类的工厂方法时,由于工厂方法是静态方法,使用起来很方便,可通过类名直接调用,而且只需要传入一个简单的参数即可,在实际开发中,还可以在调用时将所传入的参数保存在XML等格式的配置文件中,修改参数时无须修改任何Java源代码。
- 简单工厂模式最大的问题在于工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,这一点与开闭原则是相违背的。
- 简单工厂模式的要点在于:当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建细节。
- 模式结构
模式实例与解析
- 示例
模式效果与应用
- 简单工厂模式优点
- 实现了对象创建和使用的分离
- 客户端无需知道所创建的具体产品类的类名,只需要知道知道具体产品类所对应的参数即可
- 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性
- 简单工厂模式缺点
- 工厂类集中了所有产品的创建逻辑,职责过重,一旦不能正常工作,整个系统都要受到影响
- 增加系统中类的个数(引入了新的工厂类),增加了系统的复杂度和理解难度
- 系统扩展困难,一旦添加新产品不得不修改工厂逻辑
- 由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构,工厂类不能得到很好地发展
- 在以下情况下可以使用简单工厂模式:
- 工厂类负责创建的对象比较少:由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂
- 客户端只知道传入工厂类的参数,对于如何创建对象不关心:客户端既不需要关心创建细节,甚至连类名都不需要记住,只需要知道类型所对应的参数
- 简单工厂模式优点
2.2 工厂方法模式 使用频率较高
- 模式动机与定义
- 模式定义
- 工厂方法模式(Factory Method Pattern)简称工厂模式,也叫虚拟构造器模式(Virtual Constructor)或者多态工厂模式(Polymorphic Factory),它属于类创建型模式
- 在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
- 模式定义
- 模式结构与分析
- 模式结构
- 工厂方法模式包含如下角色:
- Product :抽象产品
- ConcreteProduct :具体产品
- Factory :抽象工厂
- ConcreteFactory :具体工厂
- 工厂方法模式包含如下角色:
- 模式分析
- 工厂方法模式是简单工厂模式的进一步抽象和推广。
- 由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。
- 在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。
- 这个核心类仅仅负责给出具体工厂必须实现的接口,而不负责哪一个产品类被实例化这种细节,这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。
- 当系统扩展需要添加新的产品对象时,仅仅需要添加一个具体产品对象以及一个具体工厂对象,原有工厂对象不需要进行任何修改,也不需要修改客户端,很好地符合了“开闭原则”。而简单工厂模式在添加新产品对象后不得不修改工厂方法,扩展性不好。工厂方法模式退化后可以演变成简单工厂模式。
- Java 反射机制
- 是指在程序运行时获取已知名称的类或已有对象的相关信息的一种机制,包括类的方法、属性、超类等信息,还包括实例的创建和实例类型的判断等。可通过Class类的forName()方法返回与带有给定字符串名的类或接口相关联的Class对象,再通过newInstance()方法创建此对象所表示的类的一个新实例,即通过一个类名字符串得到类的实例。配置文件应用
1
2
3
4
5
6
7//创建一个字符串类型的对象
Class c = Class.forName(“String”);
Object obj = c.newInstance();
return obj;
在实际的应用开发中,一般将具体工厂类的实例化过程进行改进,不直接使用new关键字来创建对象,而是将具体类的类名写入配置文件中,再通过Java的反射机制,读取XML格式的配置文件,根据存储在XML文件中的类名字符串生成对象。1
2
3
4
5
6
7
8
9
<?xml version="1.0"?>
<config>
<className>CashPayFactory</className>
</config>
- 是指在程序运行时获取已知名称的类或已有对象的相关信息的一种机制,包括类的方法、属性、超类等信息,还包括实例的创建和实例类型的判断等。可通过Class类的forName()方法返回与带有给定字符串名的类或接口相关联的Class对象,再通过newInstance()方法创建此对象所表示的类的一个新实例,即通过一个类名字符串得到类的实例。
- 模式结构
模式实例与解析
模式效果与应用
- 工厂方法模式的优点
- 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
- 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类。
- 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。
- 工厂方法模式的缺点
- 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
- 在以下情况下可以使用工厂方法模式:
- 一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。
- 一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
- 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
- 工厂方法模式的优点
2.3 抽象工厂模式 最复杂,有一般性
模式动机与定义
模式动机
在工厂方法模式中具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,工厂方法也具有唯一性,一般情况下,一个具体工厂中只有一个工厂方法或者一组重载的工厂方法。但是有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象。
为了更清晰地理解工厂方法模式,需要先引入两个概念:
- 产品等级结构:产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。
- 产品族:在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。
当系统所提供的工厂所需生产的具体产品并不是一个简单的对象,而是多个位于不同产品等级结构中属于不同类型的具体产品时需要使用抽象工厂模式。
抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形态。
抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建 。当一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模式更为简单、有效率。
模式定义
- 抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式。
- 模式结构与分析
- 模式结构
- 抽象工厂模式包含如下角色:
- AbstractFactory:抽象工厂
- ConcreteFactory:具体工厂
- AbstractProduct:抽象产品
- Product:具体产品
- 抽象工厂模式包含如下角色:
- 模式分析
- 模式结构
- 模式实例与解析
- 模式效果与应用
- 抽象工厂模式的优点
- 抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易。所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。另外,应用抽象工厂模式可以实现高内聚低耦合的设计目的,因此抽象工厂模式得到了广泛的应用。
- 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。这对一些需要根据当前环境来决定其行为的软件系统来说,是一种非常实用的设计模式。
- 增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。
- 抽象工厂模式的缺点
- 在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对抽象工厂角色及其所有子类的修改,显然会带来较大的不便。
- 开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦)
- 模式适用环境 在以下情况下可以使用抽象工厂模式:
- 抽象工厂模式的优点
- 模式动机与定义
- 模式动机
- 创建复杂对象
- 模式定义
- 建造者模式(Build Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
- 建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。建造者模式属于对象创建型模式。
- 将客户端与包含多个部件的复杂对象过程的创建过程分离,客户端无需知道复杂对象的内部组成与装配方式,只需要知道所需建造者的类型即可。
- 关注如何逐步创建一个复杂的对象,不同的建造者定义了不同的创建过程。
- 模式动机
模式结构与分析
- 模式结构
- 建造者模式包含以下角色
- Builder:抽象建造者
- ConcreteBuilder:具体建造者
- Director:指挥者
- Product:产品角色
- 建造者模式包含以下角色
- 模式结构
模式实例与解析
- kfc实例
模式效果与应用
- 优点
- 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 每一个具体创造者都相对独立,与其他的具体创建者无关,因此可以很方便的替换具体创建者或增加新的具体创建者,扩展方便,符合开闭原则。
- 可以更加精细地控制产品的创造过程。
- 缺点
- 建造者模式创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,不适合使用建造者模式,因此其使用范围受到一定的限制
- 如果产品内部变化复杂,可能需要定义很多具体建造者类来实现这种变化,导致系统变得庞大,增加了系统的理解难度和运行成本。
- 模式适用环境 在以下情况下可以使用建造者模式:
- 需要生成的对象有复杂的内部结构,这些产品对象通常包含多个成员变量
- 需要生成的产品对象属性相互依赖,需要指定其生成顺序
- 对象的创建过程独立于创建该对象的类,在建造者模式中通过引入了指挥者类,将创建过程封装在指挥者类中,而不在建造者类和客户类中。
- 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
- 优点
2.5 原型模式
模式动机与定义
- 模式动机
- 孙悟空变小猴子 自我复制
- 复制一个对象,从而克隆出多个与原型对象一模一样的对象 – 原型模式
- 有些对象的创建过程较为复杂,而且需要频繁创建
- 通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象
- 模式定义
- 原型模式(Prototype Pattern): 原型模式是一种对象创建型模式,用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
- 原型模式允许通过一个原型对象创建一个或多个同类型的其他对象,而无须知道任何创建的细节
- 模式动机
模式结构与分析
- 模式结构
- 角色:
- Prototype: 抽象原型类
- ConcretePrototype: 具体原型类
- Client: 客户类
- 角色:
- 模式分析
- 所有Java类都继承自java.lang.Object,而Object类提供一个clone()方法,可以将一个Java对象复制一份
- 在Java中可以直接使用Object提供的clone()方法来实现对象的克隆(浅克隆)
- 能够实现克隆的Java类必须实现一个标识接口Cloneable,表示这个Java类支持复制
- 如果一个类没有实现这个接口但是调用了clone()方法,Java编译器将抛出一个CloneNotSupportedException异常
- 浅克隆(Shallow Clone): 当原型对象被复制时,只复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没有复制,引用类型只复制地址
- 深克隆(Deep Clone): 除了对象本身被复制外,对象所包含的所有成员变量也将被复制
- 模式结构
模式实例与解析
模式效果与应用
模式优点
- 简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率
- 扩展性较好
- 简化创建结构,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品
- 可以使用深克隆的方式保存对象的状态,以便在需要的时候使用,可辅助实现撤销操作
模式缺点
- 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了开闭原则
- 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦
模式应用
- 创建新对象成本较大,新对象可以通过复制已有对象来获得,如果是相似对象,则可以对其成员变量稍作修改
- 系统要保存对象的状态,而对象的状态变化很小
- 需要避免使用分层次的工厂类来创建分层次的对象
1 |
|
2.6 单例模式
模式动机与定义
- 模式动机
- 正常情况下一个类只能生成一个实例
- 如何确保一个类只有一个实例并且这个实例易于被访问?
- 让类自身负责创建和保存它的唯一实例,并保证不能创建其他实例,并且提供一个访问该实例的方法
- 模式定义
- 单例模式(Singleton Pattern): 确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。
- 单例模式的要点有三个:
- 某个类只能有一个实例
- 必须自行创建这个实例
- 必须自行向整个系统提供这个实例
- 单例模式是一种对象创建型模式
- 模式动机
模式结构与分析
模式结构
- 角色:
- Singleton: 单例
- 角色:
模式分析
单例模式的实现
- 私有构造函数
- 静态私有成员变量(自身类型)
- 静态公有的工厂方法
饿汉式单例类(Eager Singleton)
- 一开始就创建
- 无须考虑多个线程同时访问的问题;调用速度和反应时间优于懒汉式单例;资源利用效率不及懒汉式单例;系统加载时间可能会比较长
懒汉式单例类(Lazy Singleton)
需要的时候创建
多线程问题
- 延迟加载 上锁 不能同时创建
- 双重检查锁定
- 关键字 synchronized volatile
实现了延迟加载;必须处理好多个线程同时访问的问题;需通过双重检查锁定等机制进行控制,将导致系统性能受到一定影响
模式实例与解析
模式效果与应用
- 模式效果
- 模式应用
第三章 结构型模式
3.1 适配器模式
模式动机与定义
模式动机
- 现实生活
- 不兼容 生活用电220V — 笔记本电脑 20V
- 引入 AC Adapter(交流电适配器)
- 软件开发
- 存在不兼容的结构,例如方法名不一致
- 引入适配器模式
- 现实生活
模式定义
- 讲一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作
- 适配器模式既可以作为类结构型模式,也可以是对象结构型模式
- 定义中所提及的接口是广义的接口,它可以表示一个方法或者方法的集合
模式结构与分析
模式结构
类适配器
- 适配器类继承适配者类 并调用适配者业务方法
对象适配器 使用频率更高 更加灵活 (一个类只有一个父类)
- 适配器和和适配者关联关系 适配者作为对象
适配器模式包含如下角色
- Target :目标抽象类
- Adapter:适配器类
- Adaptee:适配者类
模式分析
模式实例与解析
- 模式实例
- 模式解析
模式效果与应用
模式优点
- 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构,符合开闭原则
- 增加了类的透明性和复用性,提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用
- 灵活性和扩展性非常好
- 类适配器模式:置换一些适配者的方法很方便
- 对象适配器:可以把多个不同的适配者适配到同一个目标,还可以适配一个适配者的字类
模式缺点
类适配器模式:
- (1)一个最多只能适配一个适配者类,不能同时适配多个适配者
- (2)适配者类不能为最终类
- (3)目标抽象类只能为接口,不能为类
对象适配器:在适配器中置换适配者类的某些方法比较麻烦
应用场景:
- 系统需要使用一些现有的类,而这些类的接口不符合系统的需要,甚至没有这些类的源代码
- 常见一个可以重复使用的类,用于核一些彼此之间没有太大关联的类,包括一些可能在将来引进的类一起工作
1 | interface Adaptee { |
3.2 桥接模式
模式动机与定义
- 模式动机
- 模式定义
模式结构与分析
- 模式结构
- 模式分析
模式实例与解析
- 模式实例
- 模式实例
模式效果与应用
- 模式效果
- 模式应用
3.3 组合模式
模式动机与定义
- 模式动机
- 模式定义
模式结构与分析
- 模式结构
- 模式分析
模式实例与解析
- 模式实例
- 模式实例
模式效果与应用
- 模式效果
- 模式应用
3.4 装饰模式 取代继承的方法
模式动机与定义
- 模式动机
- 可以在不改变一个对象本身功能的基础上给对象增加额外的新行为
- 是一种用于替代继承的技术,它通过一种无须定义子类的方式给对象动态增加职责,使用对象之间的关联关系取代类之间的继承关系
- 引入装饰类,在装饰类中既可以调用待装饰的原有类的方法,还可以增加新的方法,以扩展原有类的功能
- 模式定义
- 动态地给对象增加一些格外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活
- 对象结构型模式
- 以对客户透明的方式动态地给一个对象附加上更多的责任
- 可以在不需要创建更多子类的情况下,让对象的功能得以扩展
- 模式动机
模式结构与分析
模式结构
- 包含角色
- Component:抽象构件类
- ConcreteComponent:具体构件类
- Decorator:抽象装饰类
- ConcreteDecorator:具体装饰类
- 包含角色
模式分析
透明装饰模式
- 透明(Transparent)装饰模式:要求客户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该将对象声明为具体构件类型或具体装饰类型,而应该全部声明为抽象构件类型
- 对于客户端而言,具体构件对象和具体装饰对象没有任何区别
- 可以让客户端透明地使用装饰之前的对象和装饰之后的对象,无须关心它们的区别
- 可以对一个已装饰过的对象进行多次装饰,得到更为复杂、功能更为强大的对象
- 无法在客户端单独调用新增方法addedBehavior()
半透明装饰模式
- 半透明(Semi-transparent)装饰模式:用具体装饰类型来定义装饰之后的对象,而具体构件使用抽象构件类型来定义
- 对于客户端而言,具体构件类型无须关心,是透明的;但是具体装饰类型必须指定,这是不透明的
- 可以给系统带来更多的灵活性,设计相对简单,使用起来也非常方便
- 客户端使用具体装饰类型来定义装饰后的对象,因此可以单独调用addedBehavior()方法
- 最大的缺点在于不能实现对同一个对象的多次装饰,而且客户端需要有区别地对待装饰之前的对象和装饰之后的对象
模式实例与解析
- 模式实例
- 模式实例
模式效果与应用
- 模式优点
- 对于扩展一个对象的功能,装饰模式比继承更加灵活,不会导致类的个数急剧增加
- 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的具体装饰类,从而实现不同的行为
- 可以对一个对象进行多次装饰
- 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,且原有类库代码无需改变,符合开闭原则
- 模式缺点
- 使用装饰模式进行系统设计时将产生很多小对象,大量小对象的产生势必会占用更多的系统资源,在一定程度上影响程序的性能
- 比继承更加易于出错,排错也更困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐
- 在一下情况下可以使用装饰模式
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加指责
- 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式
- 模式优点
1 |
|
3.5 外观模式 门面模式 对象结构型模式
模式动机与定义
模式动机
- 引入外观角色之后,用户只需要直接与外观角色交互,用户与子系统之间的复杂关系由外观角色来实现,从而降低了系统的耦合度。
模式定义
- 外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面
- 外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
- 外观模式又称为门面模式,它是一种对象结构型模式。
模式结构与分析
- 模式结构
- 外观模式包含如下角色:
- Facade:外观角色
- SubSystem:子系统角色
- 外观模式包含如下角色:
- 模式分析
- 根据“单一职责原则”,在软件中将一个系统划分为若干个子系统有利于降低整个系统的复杂性,一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小,而达到该目标的途径之一就是引入一个外观对象,它为子系统的访问提供了一个简单而单一的入口
- 是迪米特法则的一种体现,通过引入一个新的外观角色来降低原有系统的复杂度,同时降低客户类与子系统的耦合度
- 所指的子系统是一个广义的概念,它可以是一个类,一个功能模块、系统的一个组成部分或者一个完整的系统
- 模式结构
模式实例与解析
- 电源总开关:实例说明
- 现在考察一个电源总开关的例子,以便进一步说明外观模式。为了使用方便,一个电源总开关可以控制四盏灯、一个风扇、一台空调和一台电视机的启动和关闭。通过该电源总开关可以同时控制上述所有电器设备,使用外观模式设计该系统。
- 电源总开关:实例说明
模式效果与应用
优点
- 对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易。通过引入外观模式,客户代码将变得很简单,与之关联的对象也很少。
- 实现了子系统与客户之间的松耦合关系,这使得子系统的组件变化不会影响到调用它的客户类,只需要调整外观类即可。
- 降低了大型软件系统中的编译依赖性,并简化了系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。
- 只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类。
缺点
- 不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性。
- 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
适用环境
- 当要为一个复杂子系统提供一个简单接口时可以使用外观模式。该接口可以满足大多数用户的需求,而且用户也可以越过外观类直接访问子系统。
- 客户程序与多个子系统之间存在很大的依赖性。引入外观类将子系统与客户以及其他子系统解耦,可以提高子系统的独立性和可移植性。
- 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。
3.6 享元模式
- 模式动机与定义
- 模式结构与分析
- 模式实例与解析
- 模式效果与应用
3.7 代理模式
模式动机与定义
模式动机
- 在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务
- 通过引入一个新的对象(如小图片和远程代理对象)来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。
模式定义
- 代理模式(Proxy Pattern) :给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做Proxy或Surrogate,它是一种对象结构型模式
模式结构与分析
模式结构
- 代理模式包含如下角色:
- Subject: 抽象主题角色
- Proxy: 代理主题角色
- RealSubject: 真实主题角色
- 代理模式包含如下角色:
模式分析
- 代理模式示意结构图比较简单,一般可以简化为如下图所示,但是在现实中要复杂很多。
- 接口 实现 代理类 实现类
- 代理类 调用实现类
模式实例与解析
- 模式实例
- 论坛权限控制代理
- 在一个论坛中已注册用户和游客的权限不同,已注册的用户拥有发帖、修改自己的注册信息、修改自己的帖子等功能;而游客只能看到别人发的帖子,没有其他权限。使用代理模式来设计该权限管理模块。
- 在本实例中我们使用代理模式中的保护代理,该代理用于控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
- 论坛权限控制代理
- 模式实例
模式效果与应用
优点
- 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
- 远程代理使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
- 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。
- 保护代理可以控制对真实对象的使用权限。
缺点
- 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
+ 常见的代理模式:
+ 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又叫做大使(Ambassador)。
+ 虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
+ Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。
+ 保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
+ 缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
+ 防火墙(Firewall)代理:保护目标不让恶意用户接近。
+ 同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
+ 智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。
1 |
|
第四章 行为型模式
4.1 职责链模式
- 模式动机与定义
模式动机
- 辅导员、系主任、院长、校长都能处理奖学金申请表,他们构成一个处理申请表的链式结构,申请表沿着这条链进行传递,这条链成为职责链
- 职责链可以是一条直线,一个环或者一个树形结构,最常见的职责链是直线型,即沿着一条单向的链来传递请求
模式定义
- 职责链模式(Chain of Responsibility Pattern):避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求。将这些对象连接成一条链,并且沿着这条链传递请求,知道有对象处理它为止
- 对象行为型模式
- 模式结构与分析
模式结构
- 角色:
- Handler:抽象处理者
- ConcreteHandler:具体处理者
- 角色:
模式分析
- 将请求的处理者组成一条链,并让请求沿着链传递,由链上的处理者对请求进行相应的处理
- 客户端无须关心请求的处理细节以及请求的的传递,只需将请求发送到链上,将请求的发送者和请求的处理者解耦
- 模式实例与解析
模式实例
模式实例
- 模式效果与应用
模式优点
- 使得一个对象无须知道是其他哪一个对象处理其请求,降低了系统的耦合度
- 可简化对象之间的相互联系
- 给对象职责的分配带来更多的灵活性
- 增加一个新的具体请求处理者时无须修改原有系统的代码,只需在客户端重新建链即可
模式缺点
- 不能保证请求一定会被处理
- 对于比较长的职责链,系统性能将受到一定影响,在进行代码调试时不太方便
- 如果建链不当,可能会造成循环调用,将导致系统陷入死循环
模式应用场景
- 有多个对象可以处理同一个请求,具体哪个对象处理该请求待运行时刻再确定
- 在不明确指定接收者的情况下,像多个对象中的一个提交一个请求
- 可动态指定一组对象处理请求
- 经典应用: java中的异常处理
1 |
|
4.2 命令模式
模式动机与定义
模式动机
生活中
- 相同的开关可以通过不同的电线来控制不同的电器
- 开关 <- -> 请求发送者
- 电器 <- -> 请求的最终接收者和处理者
- 开关和电器之间并不存在直接耦合关系,它们通过电线连接在一起,使用不同的电线可以连接不同的请求接收者
软件开发
- 按钮 <- -> 请求发送者
- 事件处理类 <- -> 请求的最终接收者和处理者
- 发送者和接收者之间引入了新的命令对象(类似电线),将发送者的请求封装在命令对象中,再通过命令对象来调用接收者的方法
- 相同的按钮可以对应不同的事件处理类
模式定义
- 命令模式(Command Pattern): 将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。
- 命令模式是一种对象行为型模式,其别名为动作(Action)模式或事物(Transaction)模式
模式结构与分析
- 模式结构
- 包含角色:
- Command:抽象命令类
- ConcreteCommand:具体命令类
- Invoker:调用者
- Receiver:接收者
- 包含角色:
- 模式分析
- 将请求的发送者和接收者完全解耦
- 发送者和接收者之间没有直接引用关系
- 发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求
- 命令模式的本质是对请求进行封装
- 一个请求对应于一个命令,将发出命令的责任和执行命令的责任分开
- 模式结构
模式实例与解析
- 模式实例
- 模式解析
模式效果与应用
模式优点
- 降低系统的耦合度
- 新的命令可以很容易地加入到系统中,符合开闭原则
- 可以比较容易地设计一个命令队列或宏命令(组合命令)
- 为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案
模式缺点
- 使用命令模式可能会导致某些系统有过多的具体命令类(针对每一个对请求接收者的调用操作都需要设计一个具体命令类)
模式应用
- 需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互
- 需要在不同的时间指定请求,将请求排队和执行请求
- 需要支持命令的撤销操作和恢复操作
- 需要将一组操作组合在一起形成宏命令
1 |
|
4.3 解释器模式
模式动机与定义
- 模式动机
- Java语言无法直接解释类似”1+2+3-4+1”这样的字符串
- 定义一套文法规则来实现对这些语句的解释,即设计一个自定义语言
- 基于现有的编程语言 -> 面向对象编程语言 -> 解释器模式
- 模式定义
- 解释器模式(Interpreter Pattern): 定义一个语言的文法,并且建立一个解释器来解释该语言中的句子。
- 此处,“语言” 是指使用规定格式的语法的代码
- 它是一种类行为模式
- 模式动机
模式结构与分析
模式结构
- 包含如下角色:
- AbstractExpression:抽象表达式
- TerminalExpression:终结符表达式
- NonterminalExpression:非终结符表达式
- Context:环境类
- 包含如下角色:
模式分析
是一种使用频率相对较低但学习难度相对较大的设计模式,用于描述如何使用面向对象语言构成一个简单的语言解释器
能够加深对面向对象思想的理解,并且理解编程语言中文法规则的解释过程
文法规则
- 1 + 2 + 3 -4 + 1
- expression ::= value|operation
- operation ::= expression ‘+’ expression | expression ‘-‘ expression
- value ::= an integer //一个整数值
- ::= 表示 定义为
- | 表示 或
- { } 表示 组合
- 表示 出现0次或多次
- 1 + 2 + 3 -4 + 1
抽象语法树(AbstractSyntax Tree,AST)
- 描述了如何构成一个复杂的句子,通过对抽象语法书的分析,可以识别出语言中的终结符类和非终结符类
环境类Context
- 用于存储一些全局信息,一般包含一个HashMap或ArrayList等类型的集合对象(也可以直接由HashMap等集合类充当环境类),存储一系列公共信息,例如变量名与值的映射关系(Key/value),用于在执行具体的解释操作时从中获取相关信息
- 可以在环境类中增加一些所有表达式解释器都共有的功能,以减轻解释器的职责
- 当系统无须提供全局公共信息时可以省略环境类,根据实际情况决定是否需要环境类
模式实例与解析
模式效果与应用
模式优点
- 易于改变和扩展文法
- 可以方便地实现一个简单的语言
- 实现文法较为容易(有自动生成工具)
- 增加新的解释表达式较为方便
模式缺点
- 对于复杂文法难以维护
- 执行效率较低
模式应用
- 可以将一个需要解释执行的语言中的句子表示为一颗抽象语法树
- 一些重复出现的问题可以用一种简单的语言来进行表达
- 一个语言的文法较为简单
- 执行效率不是关键问题
1 | interface Node { |
4.4 迭代器模式(Iterator Pattern)
模式动机与定义
- 模式动机
- 电视机 – 存储电视频道的集合 – 聚合类(Aggregate Classes)
- 电视机遥控器 – 操作电视频道 – 迭代器(Iterator)
- 访问一个聚合对象中的元素但又不需要暴露它的内部节后 – 迭代器模式
- 模式定义
- 提供一种方法来访问聚合对象,而不用暴露这个对象的内部表示
- 其别名为游标(Cursor)
- 迭代器模式是一种对象行为型模式
- 模式动机
模式结构与分析
模式结构
- 模式角色:
- Iterator: 抽象迭代器
- ConcreteIterator: 具体迭代器
- Aggregate: 抽象聚合类
- ConcreteAggregate: 具体聚合类
- 模式角色:
模式分析
聚合对象的两个职责
- 存储数据,聚合对象的基本职责
- 遍历数据,既是可变化的,又是可分离的
将遍历数据的行为从聚合对象中分离出来,封装在迭代器对象中
由迭代器来提供遍历聚合对象内部数据的行为,简化聚合对象的设计,更符合单一职责原则
模式实例与解析
模式效果与应用
模式优点
- 支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式
- 简化了聚合类
- 由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,符合开闭原则
模式缺点
- 在增加新的聚合类时需要对应地增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性(跟工厂方法模式一样)
- 抽象迭代器的设计难度较大,需要充分考虑到系统将来的扩展。在自定义迭代器时,创建一个考虑全面的抽象迭代器并不是一件很容易的事情
模式应用
- 访问一个聚合对象内容而无须暴露它的内部表示
- 需要为一个聚合对象提供多种遍历方式
- 为遍历不同的聚合结构提供一个统一的接口,在该接口的实现类中为不同的聚合结构提供不同的遍历方式,而客户端可以一致性地操作该接口
1 |
|
4.5 中介者模式
模式动机与定义
- 模式动机
- 模式定义
模式结构与分析
- 模式结构
- 模式分析
模式实例与解析
- 模式实例
- 模式实例
模式效果与应用
- 模式效果
- 模式应用
4.6 备忘录模式
模式动机与定义
- 模式动机
- 模式定义
模式结构与分析
- 模式结构
- 模式分析
模式实例与解析
- 模式实例
- 模式实例
模式效果与应用
- 模式效果
- 模式应用
4.7 观察者模式
模式动机与定义
- 模式动机
- MVC示意图
- 软件系统: 一个对象的状态或行为的变化将导致其他的对象的状态或行为也发生改变,它们之间将产生联动
- 观察者模式:
- 定义了对象之间一种一对多的依赖关系,让一个对象的改变能够影响其他对象
- 发生改变的对象称为观察目标,被通知的对象称为观察者
- 一个观察目标可以对应多个观察者
- 模式定义
- 观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并自动更新。
- 观察者模式又叫做发布-订阅(Publish/Subscriber)模式,模型-视图(Model/View)模式,源-监听器(Source/Listener)模式,或从属者(Dependents)模式
- 观察者模式是一种对象行为型模式
- 模式动机
模式结构与分析
- 模式结构
- 角色:
- Subject: 目标
- ConcreteSubject: 具体目标
- Observer: 观察者
- ConcreteObserver: 具体观察者
- 角色:
- 模式分析
- 说明:
- 有时候在具体观察者类ConcreteObserver中需要使用到具体目标类ConcreteSubject中的状态(属性),会存在关联或者依赖关系
- 如果在具体层之间具有关联关系,系统的扩展性将受到一定的影响,增加新的具体目标类有时候需要修改原有观察者的代码,在一定程度上违背了开闭原则,但是如果原有观察者类无须关联新增的具体目标,则系统扩展性不受影响
- 说明:
- 模式结构
模式实例与解析
模式效果与应用
模式优点
- 可以实现表示层和数据逻辑层的分离
- 在观察目标和观察者之间建立一个抽象的耦合
- 支持广播通信,简化了一对多系统设计的难度
- 符合开闭原则,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便
模式缺点
- 将所有的观测者都通知到会花费很多时间
- 如果存在循环依赖时可能导致系统崩溃
- a调用b b里又调用a
- 没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而只是知道观察目标发生了变化
模式应用
- 一个抽象模型有两个方面,其中一个方面依赖于另一方面,将这两个方面封装在独立对象中使它们可以各自独立地改变和复用
- 一个对象的改变将导致一个或多个其他对象发生改变,且并不知道具体有多少对象将发生改变,也不知道这些对象是谁
- 需要在系统中创建一个触发链
1 | interface MyObsever { |