1、设计模式
1.1 设计模式的目的
编写软件过程中,程序员面临着来自 耦合性,内聚性,内聚性以及可维护性,可扩展性,重用性,灵活性 等多方面的挑战,设计模式是为了让 程序(软件),具有更好
- 代码重用性(即:相同功能的代码,不用多次编写)
- 可读性(即:编程规范性,便于其他程序员的阅读和理解)
- 可扩展性(即:当需要增加新的功能时,非常的方便,称为可维护)
- 可靠性(即:当我们增加新的功能后,对原来的功能没有影响)
- 是程序呈现 高内聚,低耦合的特性
1.2 设计模式的七大原则
设计模式原则,其实就是程序员在编程时,应当遵守的原则,也是各种设计模式的基础(即:设计模式为什么这样设计的依据)
设计模式常用的七大原则有:
- 单一职责原则
- 接口隔离原则
- 依赖倒转原则
- 里氏替换原则
- 开闭原则
- 迪米特法则
- 合成复用原则
1.3 单一职责原则
基本介绍
对类来说的,即一个类应该只负责一项职责。如类A负责两个不同职责:职责1,职责2.当职责1需求变更而改变A是,可能造成职责2执行错误,所以需要将类A的粒度分解为A1,A2
单一职责原则注意事项和细节
- 降低类的复杂度,一个类只负责一项职责。
- 提高类的可读性,可维护性
- 降低变更引起的风险
- 通常情况下, 我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中方法数量足够少,可以在方法级别保持单一职责原则。
1.4 接口隔离原则
基本介绍
接口隔离原则(Interface Segregation Principle)
- 客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。
- 先看一张图
- 类A通过接口Interface1依赖类B,类C通过接口Interfacce1依赖类D,如果接口Interface1对于类A和类C来说不是最小接口,那么类B和类D必须去实现他们不需要的方法
- 按隔离原则应当这样处理:将接口Interface1查分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则
接口隔离原则
- 类A通过接口Interface1依赖类B,类C通过接口Interface1依赖类D,如果接口Interface1对于类A和类C来说不是最小接口,那么类B和类D必须去实现他们不需要的方法
- 将接口Interface1拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则
- 接口Interface1中出现的方法,根据实际情况拆分为三个接口
- 代码实现
1.5 依赖倒转原则
基本介绍
依赖倒转原则(Dependence Inversion Principle)是指:
- 高层模块不应该依赖低层模块,二者都应该依赖其抽象
- 抽象不应该依赖细节,细节应该依赖抽象
- 依赖倒转(倒置)的中心思想是面向接口编程
- 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多,以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在Java中,抽象的是接口或抽象类,细节就是具体的实现类
- 使用接口或抽象类的目的是指定好规范,而不设计任何具体的操作,把展现细节的任务交给他们的实现类去完成
依赖关系传递的三种方式
- 接口传递
- 构造方法传递
- setter方式传递
依赖倒转原则的注意事项和细节
- 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好、
- 变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化。
- 继承时遵循里氏替换原则
1.6 里氏替换原则
基本介绍
- 里氏替换原则(Liskov Substitution Principle) 在1988年,由麻省理工学院的一位姓里的女士提出的。
- 如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换成o2时,程序p的行为没有发生变化,那么类型T2是类型T1的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象。
- 在使用继承时,遵循里氏替换原则,在子类中 尽量不要重写父类的方法。
- 里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖来解决问题。
1.7 开闭原则
基本介绍
- 开闭原则(Open Closed Principle)是编程中最基础、最重要的设计原则
- 一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(使用方)。用抽象构建框架,用实现扩展细节。
- 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
- 编程中遵循其他原则,以及使用设计模式的目的就是遵循开闭原则。
1.8 迪米特法则
基本介绍
- 一个对象应该对其他对象保持最少的了解
- 类与类关系越密切,耦合度越大
- 迪米特法则(Demeter Principle)又叫 最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管对么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public方法,不对外泄露任何信息。
- 迪米特法则还有个更简单的定义:只与直接的朋友通信。
直接的朋友
:每个对象都会与其他对象由耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系,耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
迪米特法则注意事项和细节
- 迪米特法则的核心是降低类之间的耦合
- 但是注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系,并不是要求完全没有依赖
1.9 合成复用原则
基本介绍
- 合成复用原则(Composite Reuse Principle)
- 原则是尽量使用合成/聚合的方式,而不是使用继承
2.0 设计原则的核心思想
- 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
- 针对接口编程,而不是针对实现编程。
- 为了交互对象之间的松耦合设计而努力。
2、UML类图
UML基本介绍
-
UML——Unified modeling language UML(统一建模语言),是一种用于软件系统分析和设计的语言工具,它用于帮助软件开发人员进行思考和记录思路的结果
-
UML本身是一套符号的规定,就像数学符号和化学符号一样,这些符号用于描述软件模型中的各个元素和他们之间的关系,比如类、接口、实现、泛化、依赖、组合、聚合等,如图:
- 使用UML来建模,常用的工具有 RationalRose,也可以使用一些插件来建模。
UML图
画UML图与写文章差不多,都是把自己的思想描述给 别人看,关键在于思路和条理,UML图分类:
- 用例图(use case)
- 静态结构图:
类图
、对象图、包图、组件图、部署图 - 动态行为图:交互图(时序图与协作图)、状态图、活动图
说明:
- 类图是描述类与类之间的关系的,是UML图中最核心的
- 在讲解设计模式时,我们必然需要会使用类图。
UML类图
- 用于描述系统中的类(对象)本身的组成和类(对象)之间的各种静态关系。
- 类之间的关系:
依赖、泛化(继承)、实现、关联、聚合与组合
类图—泛化关系(Generalization)
泛化关系实际上就是继承关系
,它是依赖关系的特例
小结:
- 泛化关系实际上就是继承关系
- 如果A类继承了B类,我们就说A和B存在泛化关系
类图—关联关系(Association)
关联关系实际上就是 类与类之间的联系,它是依赖关系的特例
关联具有导航性
:即双向关系或单向关系
关系具有多重性:如“1”(表示有且仅有一个),“0...”(表示0个或者多个),“0,1”(表示0个或者1个),“n...m”(表示n到 m个都可以),“m...*”(表示至少m个)
类图—聚合关系(Aggregation)
集合关系(Aggregation)表示的是整体和部分的关系,整体与部分可以分开
。聚合关系是关联关系的特列,所以他就有关联的导航性与多重性。
如:一台电脑有键盘、显示器、鼠标等组成;组成电脑的各个配件是可以从电脑上分离出来,使用带空心菱形的实现来表示:
类图—组合关系(Composition)
组合关系:也是整体与部分的关系,但是整体与部分不可以分开
。
3、设计模式类型
设计模式分为三种类型,共23种
- 创建型模式:
单例模式
、抽象工厂模式、原型模式、建造者模式、工厂模式
。 - 结构型模式:适配器模式、桥接模式、
装饰模式
、组合模式、外观模式、享元模式、代理模式
。 - 行为型模式:模板方法模式、命令模式、访问者模式、迭代器模式、
观察者模式
、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)。
4、单例模式
单例模式介绍
- 所谓类的单利设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类
只能存在一个对象实例
,并且该类只提供一个取得其对象实例的方法。- 比如:Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessionFactory就够,这时就会使用到单利模式。
单利设计模式八种方式
饿汉式(静态常量)
饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 懒汉式(线程安全,同步代码块)
双重检查
静态内部类
枚举
饿汉式
饿汉式(静态常量)
饿汉式(静态常量)应用实例
步骤:
- 构造器私有化
- 类的内部创建对象
- 向外暴露一个静态的公共方法。
getInstance
- 代码实现
优缺点说明:
优点
:这种写法比较简单,就是在类装在的时候就完成实例化。避免了线程同步问题。缺点
:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费- 这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,在单例模式中大多数都是调用getInstance方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到Lazy Loading的效果。
结论
:这种单例模式可用,可能造成内存浪费。
饿汉式(静态代码块)
优缺点说明:
- 这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
- 结论:这种单例模式可用,但是可能造成内存浪费。
懒汉式
懒汉式(线程不安全)
优缺点说明:
- 起到了Lazy Loading的效果,但是只能在单线程下使用。
- 如果在多线程下,一个线程进图了if(singletion == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
结论
:在实际开发中,不要使用这种方式。
懒汉式(线程安全,同步方法)
package com.king.Design_patterns;
public class Singleton {
public static void main(String[] args) {
System.out.println("懒汉式,线程安全");
Singleton1 instance = Singleton1.getInstance();
Singleton1 instance1 = Singleton1.getInstance();
System.out.println(instance == instance1);
System.out.println("instance.hashCode"+ instance.hashCode());
System.out.println("instance1.hashCode"+ instance1.hashCode());
}
}
class Singleton1{
private static Singleton1 instance;
private Singleton1(){}
/**
* 提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
* 即 懒汉式
*/
public static synchronized Singleton1 getInstance(){
if (instance == null){
instance = new Singleton1();
}
return instance;
}
}
优缺点说明:
- 解决了线程不安全问题
- 效率太低了,每个线程在想获得类的实例的时候,执行getInstance()方法都要进行同步,而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低。
结论
:在实际开发中,不推荐使用这种方式
懒汉式(线程安全,同步代码块)
优缺点说明:
- 这种方式,本意是想对第四种实现方式的改进,因为前面同步方法效率太低,该为同步产生实例化的代码块
但是这种同步并不能起到线程同步的作用。
跟第3中实现方式遇到的情形一致,加入一个线程进入了 if(singleton == null) 判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例结论
:在实际开发中,不能使用这种方式
双重检查
package com.king.Design_patterns;
public class DoubleCheck {
public static void main(String[] args) {
System.out.println("双重检查,线程安全");
Singleton2 instance = Singleton2.getInstance();
Singleton2 instance1 = Singleton2.getInstance();
System.out.println(instance == instance1);
System.out.println("instance.hashCode"+ instance.hashCode());
System.out.println("instance1.hashCode"+ instance1.hashCode());
}
}
class Singleton2{
private static volatile Singleton2 singleton2;
private Singleton2(){}
/**
* 加入双重检查,解决线程安全问题,同时解决懒加载问题
* @return
*/
public static Singleton2 getInstance(){
if(singleton2 == null){
synchronized (Singleton2.class){
if (singleton2 == null){
singleton2 = new Singleton2();
}
}
}
return singleton2;
}
}
静态内部类
优缺点:
- 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
- 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
- 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
优点
:避免了 线程不安全,利用静态内部类特点实现延迟加载,效率高。结论
:推荐使用
package com.king.Design_patterns.trpe7;
public class Test {
public static void main(String[] args) {
System.out.println("使用静态内部类完成单例模式");
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance == instance1);
System.out.println("instance.hashCode"+ instance.hashCode());
System.out.println("instance1.hashCode"+ instance1.hashCode());
}
}
//静态内部类完成
class Singleton{
private static Singleton instance;
//构造器私有化
private Singleton(){}
//写一个静态内部类,该类中有一个属性 Singleton
private static class SingletonInstance{
private static final Singleton INSTANCE = new Singleton();
}
/**
* 提供一个静态的公有方法,直接返回 SingletonInstance.INSTANCE
*/
public static synchronized Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
}
枚举
优缺点说明:
- 借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
- 这种方式在Effective Java坐着Josh Blocj 提倡的方式
结论
:推荐使用
package com.king.Design_patterns.type8;
public class SingletonTest08 {
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
Singleton instance1 = Singleton.INSTANCE;
System.out.println(instance == instance1);
System.out.println(instance.hashCode());
System.out.println(instance1.hashCode());
instance.sayOk();
}
}
/**
* 使用枚举,可以实现单例,推荐使用
*/
enum Singleton{
INSTANCE; //属性
public void sayOk(){
System.out.println("ok");
}
}
单例模式注意事项和细节说明
- 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
- 当想实例化一个单例类的时候,必须要记住使用响应的获取对象的方法,而不是使用new
- 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象是耗时过多或耗费资源过多(即:
重量级对象
),但又经常用到的对象、工具类对象
、频繁访问数据库或文件的对象(比如数据源、session工厂
等)
5、工厂模式
5.1 简单工厂模式
传统的方式的优缺点
优点
:比较好理解,简单易操作。缺点
:违反了设计模式的ocp原则,即对扩展开放,对修改关闭
。即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码。
简单工厂模式的基本介绍
- 简单工厂模式是属于创建型模式,是工厂模式的一种。
简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例
。简单工厂模式是工厂模式家族中最简单实用的模式。 - 简单工厂模式:定义了一个创建对象的类,由这个类来 封装实例化对象的行为(代码)。
- 在软件开发中,当我们会通道大量的创建某种、某类或者某批对象时,就会使用到工厂模式。
5.2 抽象工厂模式
基本介绍
- 抽象工厂模式:定义了一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的类。
- 抽象工厂模式可以将 简单工厂模式和 工厂方法模式进行整合。
- 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)。
- 将工厂抽象成两层,AbsFactory(抽象工厂) 和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂变成了
工厂簇
,更利于代码的维护和扩展。
5.3 工厂模式小结
- 工厂模式的意义
- 将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。
- 三种工厂模式(简单工厂模式、工厂方法模式、抽象工厂模式)
- 设计模式的 依赖抽象原则
- 创建对象实例时,不要直接 new 类,而是把这个 new 类的动作放在一个工厂的方法中,并返回。有的树上数,变量不要直接持有具体类的引用。
- 不要让类继承具体类,而是继承抽象类或者实现interface(接口)
- 不要覆盖基类中已经实现的方法。
6、原型模式
传统的方式的优缺点:
- 优点是 比较好理解,简单易操作。
- 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低。
- 总是需要重新初始化对象,而不是动态的获得对象运行时的状态,不够灵活
- 改进思路分析
- Java中Object类是所有类的根类,Object类提供了一个clone()方法,该方法可以将一个Java对象复制一份,但是需要实现clone的Java类必须要实现一个接口Cloneable,该接口表示该类能够赋值且具有复制的能力 => 原型模式
原型模式-基本介绍
基本介绍:
- 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
- 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节。
- 工作原理:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求额原型独享拷贝他们自己来实施创建,即 对象.clone()
- 形象的理解:孙大圣拔出猴毛,变出其他的孙大圣
深入讨论-浅拷贝和深拷贝
浅拷贝的介绍
- 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值赋值一份给新的对象。
- 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)赋值一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
- 浅拷贝是使用默认的 clone() 方法来实现
- sheep = (Sheeo) super.clone();
深拷贝的介绍
- 赋值对象的所有基本数据类型的成员变量值
- 为所有引用数据类型的成员变量申请存储空间,并赋值每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝。
- 深拷贝实现方式1:重写clone方法来实现深拷贝
- 深拷贝实现方式2:通过独享U型劣化实现深拷贝
原型模式的注意事项和细节
- 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率。
- 不用重新初始化对象,而是动态地获得对象运行时的状态
- 如果原始独享发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
- 在实现深克隆的时候可能需要比较复杂的代码
- 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则,这点请注意。
7、建造者模式
基本介绍
- 建造者模式(Builder Pattern)又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
- 建造者模式是一步一步创建一个复杂的的对象,它允许用户只通过制定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
建造者模式的四个角色
- Product(产品角色):一个具体的产品对象。
- Builder(抽象建造者):创建一个Product对象的各个部件指定的 接口/抽象类。
- ConcreteBuilder(具体建造者):实现接口,构建和装配各个部件。
- Dirctor(指挥者):构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。
建造者模式的注意事项和细节
- 客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象。
- 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也方便使用程序来控制创建过程。
- 增加新的具体建造者无需修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合 “开闭原则”。
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,
如果产品之间的差异性很大,则不适合使用建造者模式
,因此其使用范围受到一定的限制。 - 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式。
-
- 抽象工厂模式VS建造者模式
- 抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品租借,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
- 抽象工厂模式VS建造者模式
8、适配器模式
基本介绍
- 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
- 适配器模式属于结构性模式
- 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式
工作原理
- 适配器模式:将一个类的接口转换成另一种接口,让 原本接口不兼容的类可以兼容。
- 从用户的角度看不到适配者,是解耦的
- 用户调用适配器转化出来的目标接口方法,适配器再调用被是陪着的相关接口方法
- 用户收到反馈结果,感觉只是和目标接口交互,如图:
类适配器模式
注意事项和细节
- Java是单继承机制,所以类适配器需要继承src类这一点算是一个缺点,因为这要求dst必须是接口,有一定局限性
- src类的方法在Adapter中都会暴露出来,也增加了使用的成本。
- 由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了。
对象适配器模式
介绍
- 基本思路和类的适配器模式相同,只是将Adapter类作修改,而不是继承src类,而是持有src类的实例,以解决兼容性的问题。即:持有src类,山西爱你dst类接口,完成src->dst的适配
- 根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系。
- 对象适配器模式是适配器模式常用的一种
注意事项和细节
- 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承src的局限性问题,也不再要求dst必须是接口。
- 使用成本更低,更灵活。
接口适配器模式
介绍
- 一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式。
- 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接扣,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求。
- 适用于一个接口不想使用其所有的方法的情况。
适配器模式的注意事项和细节
-
三种命名方式,是根据 src 是以怎样的形式给到Adapter (在Adapter里的形式)来命名的。
-
类适配器:以类给到,在Adapter里,就是讲src当做类,继承
对象适配器:以对象给到,在Adapter里,将src作为一个对象,持有
接口适配器:以接口给到,在Adapter里,将src作为一个接口,实现
-
Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作。
-
实际开发中,实现起来不拘泥与三种经典形式。
9、桥接模式
基本介绍
- 桥接模式(Bridge模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。
- 是一种结构型设计模式
- Bridge模式基于 类的最小设计原则,通过使用使用封装、聚合即继承等行为让不同的类承担不同的职责。它的主要特点是吧抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展
原理类图
原理类图说明:
-
Client类:桥接模式的调用者
-
抽象类(Abstraction):维护了 Implementor / 即 它的实现类
ConcreteImplementorA..,二者是聚合关系,Abstraction充当桥接类
-
RefinedAbstracion:是Abstraction 抽象类的子类
-
Implementor:行为实现类的接口
-
ConcreteImplementorA / B:行为的具体实现类
-
从UML图:这里的抽象类和接口是聚合的关系,其实调用和被调用关系
桥接模式的注意事项和细节
- 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。
- 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其他的部分由具体业务来完成。
- 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
- 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程。
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性,即需要有这样的应用场景。
桥接模式其他应用场景
- 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用:
- 常见的应用场景:
- JDBC驱动程序
- 银行转账系统
- 转账分类:网上转账,柜台转账,ATM转账
- 转账用户类型:普通用户,银卡用户,金卡用户...
- 消息管理
- 消息类型:即时消息,延时消息
- 消息分类:收集短信,邮件消息,QQ消息...
10、装饰者设计模式
装饰者模式定义
- 装饰者模式: 动态的将新功能
附加到对象上
。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(OCP) - 这里提到的
动态的将新功能附加到对象
和OCP原则
,在后面的应用实例上会以代码的形式体现,请同学们注意体会。
11、组合模式
基本介绍
- 组合模式(Composite Pattern), 又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示 “整体-部分” 的层次关系。
- 组合模式一句树形结构来组合对象,用来表示部分以及整体层次。
- 这种类型的设计模式属于结构性模式。
- 组合模式使得用户对单个对象和组合对象的访问具有一致性, 即: 组合能让客户以一致的方式处理个别对象以及组合对象。
组合模式解决的问题
- 组合模式解决这样的问题,当我们的要处理的对象可以生成一颗树形结构,而我们要对树上的节点和叶子进行操作时,它能够提供一致的方式,而不用考虑它是节点还是叶子。
- 对应的示意图:
组合模式的注意事项和细节
- 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。
- 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动。
- 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构
- 需要遍历组织结构,或者处理的对象具有树形结构时,非常适合使用组合模式。
- 要求较高的抽象性, 如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式。
12、外观模式
基本介绍
- 外观模式(Facade),也叫 “过程模式”:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
- 外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而不需关心这个子系统的内部细节。
原理类图
原理类图说明(外观模式的角色)
- 外观类(Facade):为调用端提供统一的调用接口,外观类知道哪些子系统负则处理请求,从而将调用端的请求代理给适当子系统对象。
- 调用者(Client):外观接口的调用者
- 子系统的集合:指模块或者子系统,处理Facade对象指派的任务,它是功能的提供者。
注意事项和细节
- 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性。
- 外观模式对客户端与子系统的耦合关系,让子系统内部的模块更易于维护和扩展
- 通过合理的使用外观模式,可以帮我们更好的划分访问的层次。
- 当系统需要进行分层设计时,可以考虑使用Facade模式。
- 在维护一个遗留的大型系统时,可能这个系统以及变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口,让新系统与Facade类交互,提高复用性。
- 不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块号。要以让系统有层次,利于维护为目的。
13、享元模式
基本介绍
- 享元模式(Flyweight Pattern)也叫 蝇量模式:运用共享技术有效地支持大量细粒度的对象。
- 常用于系统底层开发没解决系统的性能问题。像
数据库连接池
,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个 - 享元模式能够解决
重复对象的内存浪费的问题
,当系统中有大量相似对象,需要缓冲池时。不要总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率。 - 享元模式
经典的应用场景
就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式
享元模式的原理类图
原理类图
对原理类图的说明-即(模式的角色及职责)
- FlyWeight 是抽象的享元角色,他是产品的抽象类,同时定义出对象的
外部状态
和内部状态
的接口或实现 - ConcreteFlyWeight 是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务。
- UnSharedConcreteFlyWeight 是不可共享的角色,一般不会出现在享元工厂。
- FlyWeightFactory 享元工厂类,用于构建一个池容器(集合),同时提供从池中获取对象方法。
内部状态和外部状态
享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态了,即将对象的信息分为两个部分: 内部状态和 外部状态
内部状态
是指对象共享出来的信息,存储在享元对象内部且不会碎环境的改变而改变外部状态
是指独享得以历来的一个标记,是碎环境改变而改变的、不可共享的状态、
注意事项和细节
- 在享元模式这样理解,“享” 就表示共享, “元” 表示对象。
- 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式。
- 用唯一标识码判断,如果在内存中,则返回这个唯一标识码所标识的对象,用 HashMap/HashTable存储。
- 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率。
- 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化性,不应该随着内部状态的改变而改变,这是我们
使用享元模式需要注意的地方
。 - 使用享元模式,至于划分内部状态和外部状态,并且需要有一个工厂类加以控制。
- 享元模式经典的应用场景是需要缓冲池的场景,比如 String常量池、数据库连接池。
14、代理模式
基本介绍
- 代理模式: 为一个对象
提供一个替身
,以控制对这个对象的访问。即通过代理对象访问目标对象。这样做的好处
是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。 - 被代理的对象可以是 远程对象、
创建开销大的对象
或需要安全控制的对象
。 - 代理模式有不同的形式,主要有三种 静态代理、动态代理(JDK代理、接口代理)和 Cglib代理(可以在内存动态的创建对象,而不需要事先接口,它是属于动态代理的范畴)。
静态代理
静态代理的基本介绍
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类。
应用实例:
- 定义一个接口xxx
- 目标对象xxxxdao实现该接口
- 使用静态代理方式,就需要在代理对象xxxProxt中也实现改接口
- 调用的时候通过调用代理对象的方法来调用目标对象。
- 【注意】:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法。
静态代理的优缺点:
优点
:在不修改目标对象的功能前提下,能通过代理独享对目标功能扩展。缺点
:因为代理对象需要与目标对象实现一样的接口,所以会有很对代理类。- 一旦接口增加方法,目标对象与代理独享都要维护。
动态代理
动态代理的基本介绍
- 代理对象,不需要实现接口,但是目标对象要山西爱你接口,否则不能用动态代理。
- 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象。
- 动态代理也叫作:JDK代理、接口代理。
JDK中生成代理对象的API
-
代理类所在包:java.lanhg.reflect.Proxy
-
JDK实现代理只需要使用 newProxyInstance方法,但是该方法需要接收是哪个参数。完整写法:
static Object newProxtInstace(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){ }
Cglib代理
基本介绍
- 静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是Cglib代理。
- Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将 Cglib代理归属到动态代理。
- Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口。它广泛的被许多AOP的框架使用,例如Spring AOP,实现方法拦截。
- 在AOP编程中如何选择代理模式:
- 目标对象需要实现接口,用JDK代理。
- 目标对象不需要实现接口,用Cglib代理
- Cglib包的底层是用过使用字节码处理框架ASM来转换字节码并生成新的类。
Cglib代理模式实现步骤:
- 需要引入依赖
- 在内存中动态构建子类,注意代理的类不能为final,否则报错 java.lang.IllegalArgumentException;
- 目标对象的方法,如果为final/static,那么久不会被拦截,即不会执行目标对象额外的业务方法。
代理模式(Proxy)的变体
集中常见的dialing模式介绍 — 几种变体
- 防火墙代理
- 内网通过代理穿透防火墙,实现对公网的访问。
- 缓存代理
- 比如:当请求图片文件等资源时,先到缓存代理 取,如果去到资源则ok,如果取不到资源,再到公网或者数据库取,然后缓存。
- 远程代理
- 远程对象的本地代表,通过它可以
把远程对象当本地对象
来调用。远程代理通过网络和真正的远程对象沟通信息。
- 远程对象的本地代表,通过它可以
- 同步代理
- 主要使用在多线程编程中,完成多线程间同步工作。
15、模板模式
基本介绍
- 模板方法模式(Template Method Pattern),又叫模板模式(Tempalte Pattern),在一个抽象类公开定义了它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
- 简单说, 模板方法模式 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法 的结构,就可以重定义概算发的某些特定步骤。
- 这种类型的设计模式属于行为型模式。
模板方法模式原理类图
模板方法模式原理类图
对原理类图的说明—即(模板方法模式的角色及职责)
- AbstractClass抽象类,类中实现了模板方法(Template),定义了算法的骨架,具体子类需要去实现,其他的抽象方法operationr2 , 3 ,4
- ConcreteClass 实现抽象方法 operationr2 , 3, 4,以完成算法中特点子类的步骤。
模板方法模式的钩子方法
- 在模板方法模式的父类中,我们可以定义一个方法。它默认不做任何事情,子类可以视情况要不要覆盖它,该方法称为 “钩子”。
模板方法模式的注意事项和细节
- 基本思想是: 算法值存在一个地方,也就是父类中,容易修改。 需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改。
- 实现了最大化代码复用。 父类的模板方法和已山西爱你的某些步骤会被子类继承而直接使用。
- 既统一了算法,也提供了很大的灵活性。 父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。
- 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大。
- 一般模板方法都加上 final 关键字,防止子类重写模板方法。
- 模板方法模式使用场景:
当要完成在某个过程
, 该过程要执行一系列步骤, 这一系列的步骤基本相同,但其个别步骤
在实现时可能不同,通常考虑用模板方法模式来处理。
16、命令模式
基本介绍
- 命令模式(Command Pattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需要在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计
- 命令模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
- 在命令模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命令),同时命令模式也支持可撤销的操作。
- 通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。
- Invoker是调用者(将军),Receiver是被调用者(士兵),MyCommand是命令,陕西西安了Command接口,持有接收对象
原理类图
对原理类图的说明—即(命令模式的角色及职责)
- Invoker是调用者角色
- Command 是命令角色,需要执行的所有命令都在这里,可以是接口或抽象类
- Receiver:接收者角色,知道如何实施和执行一个请求相关的操作。
- ConcreteCommand:将一个接收者对象与一个动作绑定,调用接收者相应的操作,实现execute
注意事项和细节
- 将发起请求的对象与执行球球的独享解耦。
- 发起请求的对象是调用者,调用者只要调用命令独享的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:“请求发起者” 和 “请求执行者” 之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。
- 容易设计一个命令队列。只要把命令对象放到队列,就可以多线程的执行命令
- 容易实现对请求的撤销和重做
- 命令模式的不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在使用的时候要注意
- 空命令也是一种设计模式的,它为我们省去了判空的操作。
- 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟CMD(DOS命令)订单的撤销/恢复、触发-反馈机制
17、访问者模式
基本介绍
- 访问者模式(Visitor Pattern),封装一些作用于某种数据结构的个元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
- 主要讲数据结构与数据操作分离, 解决
数据结构
和操作解耦
问题。 - 访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者的接口。
- 访问者模式主要应用场景是: 需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),同时需要避免让这些操作 “污染” 这些对象的类,可以选用访问者模式解决。
原理类图
原理类图:
对原理类图的说明 — 即(访问者模式的角色及职责)
- Visitor 是抽象访问者,为该独享结构中的 ConcreteElement的每一个类声明一个 visit 操作。
- ConcreteVisitor: 是一个具体的访问者,实现每个有 Visitor 声明的操作,是每个操作实现的部分。
- ObjectStructure 能枚举它的元素,可以提供一个高层的接口,用来允许访问者访问元素。
- Element 定义一个 accept 方法,接收一个访问者对象。
- ConcreteElement 为具体元素,实现了 accept 方法。
双分派
- 所谓双分派是指不管类怎么变化,我们都能找到期望的方法运行。双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型。
注意事项和细节
- 优点
- 访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高。
- 访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于过滤器,适用于数据结构相对稳定的系统。
- 缺点
- 具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的,这样造成了具体元素变更比较困难。
- 违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素。
- 因此,如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的。
18、迭代器模式
基本介绍
- 迭代器模式(Iterator Pattern)是常用的设计模式,属于行为型模式。
- 如果我们的
集合元素是用不同的方式实现
的,有数组,还有java 的集合类,或者还有其他方式,当客户端要 遍历这些集合元素 的时候就要使用多种遍历方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。 - 迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素,不需要知道集合对象的底层表示,即:不暴露其内部的结构。
迭代器模式的原理类图
原理类图
对原理类图的说明 —即(迭代器模式的角色及职责)
- Iterator:迭代器接口,是系统提供,含义 hasNext, next , remove
- ConcreteIterator:具体的迭代器类,管理迭代
- Aggregate:一个统一的聚合接口,将客户端和具体聚合解耦。
- ConcreteAggreage:具体的聚合持有对象集合,并提供一个方法,返回一个迭代器,该迭代器可以正确遍历集合。
- Client:客户端,通过Iterator 和 Aggregate 依赖子类。
注意事项和细节
- 优点
- 提供了一个统一的方法遍历对象,客户不用再考虑聚合的类型,使用一种方法就可以遍历对象了。
- 隐藏了聚合的内部结构,客户端要遍历聚合的时候只能取到迭代器,而不会知道聚合的具体组成。
- 提供了一种设计思想,就是一个类应该只有一个引起变化的原因 (叫做
单一职责原则
)。在聚合类中,我们把迭代器分开,就是要把管理对象集合和遍历对象集合的责任分开,这样来集合改变的话,只影响到聚合对象。而如果遍历方式改变的话,只影响到了迭代器。 - 当要展示一组相似对象,或者遍历一组相同对象时使用。适合使用迭代器模式。
- 缺点
- 每个聚合对象都要一个迭代器,会生成多个迭代器 不好管理类。
19、观察者模式
观察者模式原理
Subject |
---|
registerObjserver() |
removeObserver() |
notifyObservers() |
-
观察者模式类似定牛奶业务
- 奶茶/气象局:Subject
- 用户/第三方网站:Observer
-
Subject:登记注册、移除和通知
- registerObserver 注册
- removeObserver 移除
- notifyObserver() 通知所有的注册的用户,根据不同需求,可以是更新数据,让用户来取,也可能是实时推送,看具体的需求来定
-
Observer:接收输入
-
Obsever update()
-
-
观察者模式:对象之间
多对以依赖
的一种设计方案,被依赖的对象为Subject,依赖的对象为Observer,Subject通知Observer变化,比如者里的奶站是Subject,是1的一方。用户Observer,是多的一方。
观察者模式的好处
- 观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除和通知。
- 这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类 WeathData不会修改代码,遵守了ocp原则。
20、中介者模式
基本介绍
- 中介者模式(Mediator Pattern),用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
- 中介者模式属于行为模式,是代码易于维护
- 比如MVC模式,C(Controller控制器)是M(Model模型)和V(View视图)的中介者,在前后端交互时起到了中间人的作用。
原理类图UML
对原理类图的说明-即(中介者模式的角色及职责)
- Mediator就是抽象中介者,定义了同事对象到中介者对象的接口
- Colleague 是抽象同事类
- ConcreteMediator 具体的中介者对象,实现抽象方法,他需要知道所有的具体的同事类,即以一个集合来管理HashMap,并接受某个同事对象消息,完成响应的任务
- ConcreteColleague具体的同事类,会有很多,每个同事只知道自己的行为,而不了解其他同事类的行为(方法),但是他们都依赖中介者对象。
注意事项和细节
- 多个类相互耦合,会形成网状结构,使用中介者模式将网状结构分离为星型结构,进行解耦。
- 减少类间依赖,降低了耦合,符合迪米特原则。
- 中介者承担了较多的责任,一旦中介者出现了问题,整个系统就会受到影响
- 如果设计不当,中介者对象本身变得过于复杂,这点在实际使用时,需特别注意。
21、备忘录模式
基本介绍
- 备忘录模式(Memento Pattern)在不破坏封装线的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,并在该对象之外保存这个状态。这样以后就可将该对象回复到原先保存的状态。
- 可以这里理解备忘录模式:现实生活中的备忘录是用来记录某些要去做的事情,或者是记录以及达成的共同意见的事情,一方忘记了。而在软件层面,备忘录模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作
- 备忘录模式属于行为型模式
UML类图
对原理类图的说明-及(备忘录模式的角色及职责)
- oroginator:对象(需要保存状态的对象)
- Memento:备忘录对象。负责保存好记录,即Originator内部状态
- Caretaker:守护者对象,负责保存多个备忘录对象,使用集合管理,提高效率。
- 说明:如果希望保存多个originator对象的不同时间的状态,也可以,只需要 HashMap<String , 集合>
注意事项和细节
- 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态
- 实现了信息的封装,使得用户不需要关心状态的保存细节
- 如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
- 适用场景:1、后悔药。 2、打游戏时的存档 3、Windows里的 ctrl + z 4、IE中的后腿 5、数据库的事务管理
- 为了节约内存,备忘录模式可以和圆心模式配合使用。
22、解释器模式
基本介绍
- 在编译原理中,一个算数表达式通过 词法分析器 形成词法单元,而后这些词法单元再通过 语法分析器 构造语法分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器
- 解释器模式(Interpreter Pattern):是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)
- 应用场景
- 应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
- 一些重复出现的问题可以用一种简单的语言来表示
- 一个简单语法需要解释的场景
- 这样的例子还有,比如 编译器、运算表达式计算、正则表达式、机器人等。
UML原理类图
对解释器原理类图的说明-及(解释器模式的角色及职责)
- Context:是环境角色,含有解释器之外的全局信息
- AbstractExpression:抽象表达式,声明一个抽象的解释器操作,这个方法为抽象语法树中所有的节点所共享
- TerminalExpression:为终结符表达式,实现与文法中的终结符相关的解释操作。
- NoTermialExpression:为非终结符表达式,为文法中的非终结符实现解释操作
- 说明:输入Context he TerminalExpression 信息通过Client输入即可。
注意事项和细节
- 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性
- 应用场景:编译器、运算表达式、正则表达式、机器人等
- 使用解释器可能带来的问题:解释器模式会引起类膨胀、解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能降低。
23、状态模式
基本介绍
- 状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一 一 对应的,状态之间可以互相转换
- 当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类。
UML原理类图
对原理类图的说明-及(状态模式的角色及职责)
- Context类为环境角色,用于维护State实例,这个实例定义当前状态
- State是抽象状态角色,定义一个接口封装与Context的一个特点接口相关行为
- ConcreteState具体的状态角色,每个子类实现一个Context的一个状态相关行为。
注意事项和细节
- 代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中
- 方便维护。将容易昌盛问题的if-else语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产生很多if-else语句,而且容易出错。
- 符合 “开闭原则”。容易增删状态
- 会产生很多类。每个状态都要有一个对应的类,当状态过多时,会产生或多类,加大维护难度。
- 应用场景:当一个事件或者对象由很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式。
24、策略模式
基本介绍
- 策略模式(Strategy Pattern):定义算法簇,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
- 这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口);第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)
UML原理类图
说明:
- 从上图可以看到,客户context有成员变量strategy或者其他的策略接口,至于需要使用到哪个策略,我们可以在构造器中指定。
注意事项和细节
- 策略模式的关键是:
分析项目中变化部分与不变部分
- 策略模式的核心思想是:多用组合/聚合 少用继承;用行为类组合,而不是行为的继承。更有弹性。
- 体现了 “对修改关闭,对扩展开放” 原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可,避免了使用多重转移语句(if..else if...else)
- 提供了可以替换继承关系的办法:策略模式将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展
- 需要注意的是:每添加一个策略就要增加一个类。当策略过多是会导致类数目庞大
25、职责链模式
基本介绍
- 职责链模式(Chain of Responsibility Pattern),又叫 责任链模式,为请求创建了一个
接收者对象的链(简单示意图)
。这种模式对请求的发送者和接收者进行解耦。 - 职责链模式通常每个接收者都包含另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,以此类推。
- 这种类型的设计模式属于行为模式。
UML原理类图
对原理类图的说明-即(职责链模式的角色即职责)
- Handler:抽象的处理者,定义了一个处理请求的接口,同时含义另外Handler
- ConcreteHanlerA,B:是具体的处理者,处理它自己负责的请求,可以访问它的后继者(即下一个处理者),如果可以处理当前请求,则处理,否则就将该请求交给 后继者去处理,从而形成一个职责链
- Request,含有很多属性,表示一个请求。
注意事项和细节
- 将请求和处理分开,实现解耦,提高系统的灵活性
- 简化了对象,使对象不需要知道链的结构
- 性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在Handler中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出来超长链无意识地破坏系统性能
- 调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂
- 最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪等审批流程、java web中Tomcat对Encoding的处理、拦截器