Java的23种设计模式
一、基本概述
1. 设计模式的目的
编写软件过程中,程序员面临着来自耦合性,内聚性以及可维护性,可扩展性,重用性,灵活性 等多方面的挑战,设计模式是为了让程序(软件),具有更好
- 代码重用性 (即:相同功能的代码,不用多次编写)
- 可读性 (即:编程规范性, 便于其他程序员的阅读和理解)
- 可扩展性 (即:当需要增加新的功能时,非常的方便,称为可维护)
- 可靠性 (即:当我们增加新的功能后,对原来的功能没有影响)
- 使程序呈现高内聚, 低耦合的特性
- 设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,模式不是代码,而是某类问题的通用解决方案, 设计模式(Design pattern)代表了最佳的实践。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。设计模式的本质提高 软件的维护性,通用性和扩展性,并降低软件的复杂度。
2. 设计模式的七大原则★
2.1 单一职责原则
-
描述:
对类来说的,即一个类应该只负责一项职责。如类A负责两个不同职责:职责1,职责2。当职责1需求变更而改变A时,可能造成职责2执行错误, 所以需要将类A的粒度分解为A1, A2
-
目的:
- 降低类的复杂度,一个类只负责一项职责。
- 提高类的可读性,可维护性
- 降低变更引起的风险
- 通常情况下, 我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中方法数量足够少,可以在方法级别保持单一职责原则
2.2 接口隔离原则
-
描述:
客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上
2.3 依赖倒转(倒置)原则
- 描述:
- 高层模块不应该依赖低层模块,二者都应该依赖其抽象
- 抽象不应该依赖细节,细节应该依赖抽象
- 依赖倒转(倒置)的中心思想是面向接口编程
- 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象指的是接口或抽象类,细节就是具体的实现类
- 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成
2.4 里氏替换原则
- 描述:
- 里氏替换原则(Liskov Substitution Principle)在1988年,由麻省理工学院的以为姓里的女士提出的。
- 如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象。
- 在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法。
- 里氏替换原则告诉我们,继承实际上让两个类耦合性增强了, 在适当的情况下,可以通过聚合,组合,依赖 来解决问题。
2.5 开闭原则
- 描述:
- 开闭原则(Open Closed Principle) 是编程中最基础、最重要的设计原则
- 一个软件实体如类,模块和函数应该对扩展开放(对提供方), 对修改关闭(对使用方)。 用抽象构建框架,用实现扩展细节。
- 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
- 编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则
2.6 迪米特法则
-
描述:
- 一个对象应该对其他对象保持最少的了解
- 类与类关系越密切,耦合度越大
- 迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public 方法,不对外泄露任何信息
- 迪米特法则还有个更简单的定义:只与直接的朋友通信
- 直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
-
目的:
- 迪米特法则的核心是降低类之间的耦合
- 但是注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系, 并不是要求完全没有依赖关系
2.7 合成复用原则
-
描述:
尽量使用合成/聚合的方式,而不是使用继承
小结
- 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
- 针对接口编程,而不是针对实现编程。
- 为了交互对象之间的松耦合设计而努力
二、UML类图
描述
- UML——Unified modeling language UML(统一建模语言),是一种用于软件系统分析和设计的语言工具,它用于帮助软件开发人员进行思考和记录思路的结果
- UML本身是一套符号的规定,就像数学符号和化学符号一样,这些符号用于描述软件模型中的各个元素和他们之间的
关系,比如类、接口、实现、泛化、依赖、组合、聚合等 - 使用UML来建模,常用的工具有
Rational Rose
, 也可以使用一些插件来建模
UML图形说明
图形 | PlantUml | 含义 |
---|---|---|
![]() | class A | 类A |
![]() | abstract class A | 抽象类A |
![]() | interface A{ T start();} | 接口A |
![]() | enum A{ ONE, TWO, THREE;} | 枚举A |
![]() | A<|-B(横向),A<|–B(纵向),增加--- 延长 | B泛化A |
![]() | A<.B(横向),A<…B(纵向),增加... 延长 | B依赖A |
![]() | A-B(横向),A–B(纵向),增加--- 延长 | B关联A |
![]() | A->B(横向),A–>B(纵向),增加--- 延长 | A关联B(单向) |
![]() | A<|.B(横向),A<|…B(纵向),增加... 延长 | B实现A |
![]() | A *-B(横向),A *–B(纵向),增加--- 延长 | A组合B |
![]() | A o-B(横向),A o–B(纵向),增加--- 延长 | A聚合B |
可访问性说明
Character | Icon for field | Icon for method | Visibility |
---|---|---|---|
- | ![]() | ![]() | private |
# | ![]() | ![]() | protected |
~ | ![]() | ![]() | package private |
+ | ![]() | ![]() | public |
关系说明
-
依赖关系(Dependence)
- 类中用到了对方
- 如果是类的成员属性
- 如果是方法的返回类型
- 是方法接收的参数类型
- 方法中使用到
-
泛化关系(Generalization)
- 泛化关系实际上就是继承关系,是依赖关系的特例
- 如果A类继承了B类,我们就说A和B存在泛化关系
-
实现关系(Implementation)
- 实现关系实际上就是A类实现B接口,也是依赖关系的特例
-
关联关系(Association)
- 关联关系实际上就是类与类之间的联系,也是依赖关系的特例
- 关联具有导航性:即双向关系或单向关系
- 关系具有多重性:如“1”(表示有且仅有一个),“0…”(表示0个或者多个),“0, 1”(表示0个或者一个),“n…m”(表示n到 m个都可以),“m…*”(表示至少m个)
-
聚合关系(Aggregation)
- 聚合关系表示的是整体和部分的关系,整体与部分可以分开。
- 聚合关系是关联关系的特例,所以他具有关联的导航性与多重性。
- A类的对象在创建时不会立即创建B类的对象,而是等待一个外界的对象传给它,则A中聚合了B
-
组合关系(Composition)
- 组合关系也是整体与部分的关系,但是整体与部分不可以分开。
- 一般是A类的构造方法里创建B类的对象,则A中组合了B
三、创建型模式
1. 单例模式★
-
描述
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类
只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)
1.1 饿汉式(静态常量)★
class Test{
//本类内部创建静态对象实例
private static final Test test=new Test();
//构造器私有化
private Test(){}
//对外提供公共方法获取静态实例对象
public static Test getInstance(){
return test;
}
}
-
优缺点说明
- 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
- 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费
这种方式基于classloder机制避免了多线程的同步问题,不过, instance在类装载时就实例化,在单例模式中大多数都是调用getInstance方法, 但是导致类装载的原因有很多种, 因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到lazy loading的效果 - 结论:这种单例模式可用, 可能造成内存浪费
1.2 饿汉式(静态代码块)
class Test{
private static Test test;
//在静态代码块中创建实例
static {
test=new Test();
}
//构造器私有化
private Test(){}
//获取静态实例对象
public static Test getInstance(){
return test;
}
}
- 优缺点说明
- 这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
- 结论: 这种单例模式可用,但是可能造成内存浪费
1.3 懒汉式(线程不安全)
class Test {
private static Test test;
//构造器私有化
private Test() {}
//调用getInstance方法时才创建对象
public static Test getInstance() {
if (test == null) {
test = new Test();
}
return test;
}
}
- 优缺点说明
- 起到了Lazy Loading的效果,但是只能在单线程下使用。
- 如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式
- 结论:在实际开发中,不要使用这种方式.
1.4 懒汉式(线程安全,同步方法)
class Test {
private static Test test;
//构造器私有化
private Test() {}
//调用getInstance方法时才创建对象,加入同步代码解决线程同步问题
public static synchronized Test getInstance() {
if (test == null) {
test = new Test();
}
return test;
}
}
- 优缺点说明
- 解决了线程不安全问题
- 效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低
- 结论: 在实际开发中,不推荐使用这种方式
1.5 懒汉式(线程安全,同步代码块)
class Test {
private static Test test;
//构造器私有化
private Test() {}
public static Test getInstance() {
if (test == null) {
synchronized (Test.class){
test = new Test();
}
}
return test;
}
}
- 优缺点说明
- 这种方式,本意是想对第四种实现方式的改进,因为前面同步方法效率太低,改为同步产生实例化的的代码块
- 但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了
if (test== null)
判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例 - 结论:在实际开发中, 不能使用这种方式
1.6 懒汉式(双重检查)★
class Test {
private static volatile Test test;
//构造器私有化
private Test() {}
//双重检查
public static Test getInstance() {
if (test == null) {
synchronized (Test.class){
if (test == null) {
test = new Test();
}
}
}
return test;
}
}
- 优缺点说明
- 双重检查概念是多线程开发中常使用到的, 如代码中所示,我们进行了两次if (test== null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (test== null),直接return实例化对象,也避免的反复进行方法同步。
- 线程安全;延迟加载;效率较高
- 结论:在实际开发中,推荐使用这种单例设计模式
1.7 静态内部类★
class Test {
//构造器私有化
private Test() {}
public static Test getInstance() {
return TestInstance.TEST;
}
//静态内部类
private static class TestInstance{
private static final Test TEST=new Test();
}
}
- 优缺点说明
- 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。静态内部类方式在Test类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载TestInstance类,从而完成Test的
实例化。类的静态属性只会在第一次加载类的时候初始化,所以在这里, JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。 - 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
- 结论:推荐使用.
- 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。静态内部类方式在Test类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载TestInstance类,从而完成Test的
1.8 枚举★
enum Test{
INSTANCE;
//设置属性
private Integer id;
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public void method{
//业务代码
}
}
- 优缺点说明
- 这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
- 这种方式是Effective Java作者Josh Bloch 提倡的方式
- 结论:推荐使用
1.9 源码分析
public class Runtime {
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
在jdk的java.lang.Runtime包中使用了饿汉式(静态常量)的单例模式
- 单例模式小结
- 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
- 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new
- 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象), 但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、 session工厂等)
2. 工厂模式(包含两种)★
2.1 简单工厂模式(静态工厂模式)
-
描述
- 简单工厂模式是属于创建型模式,是工厂模式的一种。 简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式
- 简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)
- 在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式
-
UML
- 使用前
-
使用后
//简单工厂类,工厂对象一般是单例
enum SimpleFactory {
INSTENCE;
public AbstractClass getAbstractClass(Integer type){
switch (type){
case 1:
new RealizeClass1();
break;
case 2:
new RealizeClass2();
break;
}
return null;
}
}
//抽象类
abstract class AbstractClass {
public abstract void fun();
}
//实现类1
class RealizeClass1 extends AbstractClass{
@Override
public void fun() {
//RealizeClass1逻辑
}
}
//实现类2
class RealizeClass2 extends AbstractClass{
@Override
public void fun() {
//RealizeClass2逻辑
}
}
-
优缺点说明
使用了简单工厂模式后可以解除
use
和RealizeClass
之间的耦合,可以使得增加新的RealizeClass
时不需要对多个use
进行修改。只需要统一修改唯一的SimpleFactory
即可。但是当需要不同的
SimpleFactory
以提供满足不同需求的对象时,扩展性不够
2.2 工厂方法模式★
-
描述
定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
-
UML
/**
* use要使用时在自己类中通过调用对应RealizeFactory对象的create方法来获取对应RealizeClass对象
*/
//抽象工厂方法
abstract class AbstractFactory{
public abstract void create();
}
//生产Class1的工厂实现类
class RealizeFactory1 extends AbstractFactory{
@Override
public void create() {
//用于实现生产RealizeClass1的逻辑
}
}
//生产Class2的工厂实现类
class RealizeFactory2 extends AbstractFactory{
@Override
public void create() {
//用于实现生产RealizeClass2的逻辑
}
}
//抽象类
abstract class AbstractClass {
public abstract void fun();
}
//实现类1
class RealizeClass1 extends AbstractClass{
@Override
public void fun() {
//RealizeClass1逻辑
}
}
//实现类2
class RealizeClass2 extends AbstractClass{
@Override
public void fun() {
//RealizeClass2逻辑
}
}
-
优缺点说明
使用者只需要找到对应产品的工厂类便能获取到所需的产品对象,并不需要关系产品生产的细节,也便于扩展。当新增产品时只要同时再增加一个对应的工厂类即可,符合开闭原则
但是缺点就是会使得系统复杂度增加。每次增加一个产品就需要增加至少两个类(产品和工厂)
2.3 抽象工厂模式★
- 描述
- 抽象工厂模式:定义了一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的类
- 抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
- 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)。
- 将工厂抽象成两层, AbsFactory(抽象工厂) 和 具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。 这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。
- UML
/**
* use要使用时给自己类中InterfaceFactory属性注入对应的实现对象,并调用对象的create方法来获取对应RealizeClass对象
*/
//抽象工厂方法
interface InterfaceFactory{
void create();
}
//生产Class1的工厂实现类
class RealizeFactory1 implements InterfaceFactory{
@Override
public void create() {
//用于实现生产RealizeClass1的逻辑
}
}
//生产Class2的工厂实现类
class RealizeFactory2 implements InterfaceFactory{
@Override
public void create() {
//用于实现生产RealizeClass2的逻辑
}
}
//抽象类
abstract class AbstractClass {
public abstract void fun();
}
//实现类1
class RealizeClass1 extends AbstractClass{
@Override
public void fun() {
//RealizeClass1逻辑
}
}
//实现类2
class RealizeClass2 extends AbstractClass{
@Override
public void fun() {
//RealizeClass2逻辑
}
}
-
优缺点说明
抽象工厂模式除了具有工厂方法模式的优点外,最主要的优点就是程序员可以根据创建对象类型使用对应的工厂子类。 这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。
但是工厂簇的扩展将是一件十分费力的事情,假如工厂簇中需要增加一个新的产品,则几乎所有的工厂类都需要进行修改。所以使用抽象工厂模式时,对产品等级结构的划分是非常重要的
2.4 源码分析
//Calendar.getInstance()
public static Calendar getInstance()
{
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}
private static Calendar createCalendar(TimeZone zone,Locale aLocale)
{
CalendarProvider provider =
LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
.getCalendarProvider();
if (provider != null) {
try {
return provider.getInstance(zone, aLocale);
} catch (IllegalArgumentException iae) {
// fall back to the default instantiation
}
}
Calendar cal = null;
if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype != null) {
switch (caltype) {
case "buddhist":
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanese":
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory":
cal = new GregorianCalendar(zone, aLocale);
break;
}
}
}
if (cal == null) {
// If no known calendar type is explicitly specified,
// perform the traditional way to create a Calendar:
// create a BuddhistCalendar for th_TH locale,
// a JapaneseImperialCalendar for ja_JP_JP locale, or
// a GregorianCalendar for any other locales.
// NOTE: The language, country and variant strings are interned.
if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
cal = new BuddhistCalendar(zone, aLocale);
} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
&& aLocale.getCountry() == "JP") {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
}
return cal;
}
在jdk的java.util.Calendar包中使用了简单工厂模式
-
工厂模式小结
- 工厂模式的意义:将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。
- 三种工厂模式 (简单工厂模式、工厂方法模式、抽象工厂模式)
- 设计模式的依赖抽象原则
- 创建对象实例时,不要直接 new 类, 而是把这个new 类的动作放在一个工厂的方法中,并返回。有的书上说,变量不要直接持有具体类的引用。
- 不要让类继承具体类,而是继承抽象类或者是实现interface(接口)
- 不要覆盖基类中已经实现的方法。
3. 原型模式
3.1 原型模式
- 描述
- 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型, 创建新的对象
- 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
- 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即obj.clone()
- UML
//使用类
class Use{
public static void main(String[] args) {
Prototype prototype = new Prototype(1);
try {
//拷贝prototype对象,创建一个属性一样的新对象
Prototype clone = (Prototype)prototype.clone();
Prototype clone1 = (Prototype)prototype.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
//实现Cloneable接口才可以使用clone
class Prototype implements Cloneable{
private Integer id;
public Prototype(Integer id) {
this.id = id;
}
//克隆该实例,使用默认的clone方法来完成
@Override
protected Object clone() throws CloneNotSupportedException {
//可以自定义逻辑
return super.clone();
}
}
- 优缺点说明
- 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
- 不用重新初始化对象,而是动态地获得对象运行时的状态
- 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
- 在实现深克隆的时候可能需要比较复杂的代码
- 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则(开闭原则)。
3.2 源码分析
- application.xml
<!--原型模式创建的bean-->
<bean id="id01" class="com.atguigu.spring.bean.Monster" scope="prototype"/>
- spring源码
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取monster[通过id获取monster]
Object bean = applicationContext.getBean("id01");
System.out.println("bean" + bean);
- getBean()
@Override
public Object getBean(String name) throws BeansException{
return doGetBean(name,null,null,false);
}
//doGetBean()
protected <T> doGetBean(final String name, ...) throws BeansException{
//省略部分代码
//判断是否为单例模式
if(mbd.isSingleton()){
//省略代码
}
//判断是否为原型模式
else if(mbd.isPrototype()){
Object prototypeInstance = null;
try{
beforePrototypeCreation(beanName);
//createBean返回原型对象
prototypeInstance = createBean(beanName,mbd,args);
}
finally{
afterPrototypeCreation(beanName);
}
//省略代码
}
}
在Spring的IOC实现中使用了原型模式
3.3 深拷贝和浅拷贝
- 浅拷贝
- 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
- 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
- 浅拷贝是使用默认的 clone()方法来实现。
- 深拷贝
- 复制对象的所有基本数据类型的成员变量值。
- 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝。
- 深拷贝实现方式:
- 重写clone方法来实现深拷贝
- 通过对象序列化实现深拷贝(推荐)
- 反射
简单概括:浅拷贝是对象中基本数据类型值传递,引用数据类型引用传递;深拷贝是对象中所有属性值传递。
- 深拷贝代码实现
//通过序列化方式实现深拷贝
class Prototype implements Serializable,Cloneable{
private static final long serialVersionUID = 1L;
//使用默认clone方法是浅拷贝
@Override
protected Object clone() throws CloneNotSupportedException {
//可以自定义逻辑
return super.clone();
}
//深拷贝
public Object deepClone(){
//创建流对象
ObjectOutputStream oos=null;
ObjectInputStream ois=null;
try{
//序列化
oos = new ObjectOutputStream(new ByteArrayOutputStream());
oos.writeObject(this);//当前这个对象以对象流的方式输出
//反序列化
ois=new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
return (Prototype)ois.readObject();
}catch (Exception e){
e.printStackTrace();
return null;
}finally {
//关闭流
}
}
}
- 生产环境方案
//把对象转为json再转回对象,本质是反射
private Object deepCopyByJson(Object obj) {
String json = JSON.toJSONString(obj);
return JSON.parseObject(json, Object.class);
}
//序列化
(New)SerializationUtils.clone(origin);
4. 建造者模式
4.1 建造者模式
- 描述
- 建造者模式(Builder Pattern) 又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
- 建造者模式 是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
- 建造者模式的四个角色
- Product(产品角色): 一个具体的产品对象。
- Builder(抽象建造者): 创建一个Product对象的各个部件指定的 接口/抽象类。
- ConcreteBuilder(具体建造者): 实现接口,构建和装配各个部件。
- Director(指挥者): 构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用:
- 一是隔离了客户与对象的生产过程
- 二是负责控制产品对象的生产过程。
- UML
//抽象建造者
interface Builder{
void buildPartA();
void buildPartB();
void buildPartC();
Object getResult();
}
//产品
class Product{
}
//具体建造者
class ConcreteBuilder implements Builder{
//实现ABC的逻辑
@Override
public void buildPartA() { }
@Override
public void buildPartB() { }
@Override
public void buildPartC() { }
@Override
public Product getResult() {
return new Product();
}
}
//指挥者
class Director{
private Builder builder;
//注入builder
public void setBuilder(Builder builder){
this.builder=builder;
}
public Product getResult(){
//由指挥者确定执行过程返回结果
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return (Product) builder.getResult();
}
}
-
优缺点说明
- 客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
- 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象
- 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程
- 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合 “开闭原则”
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似, 如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
- 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大, 因此在这种情况下,要考虑是否选择建造者模式.
-
抽象工厂模式VS建造者模式
-
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。
-
而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品
-
4.2 源码分析
//StringBuilder是指挥者也是具体建造者
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
/** use serialVersionUID for interoperability */
static final long serialVersionUID = 4383685877147921099L;
//AbstractStringBuilder虽然是抽象类但也是是具体建造者,其中实现类大部分append方法
abstract class AbstractStringBuilder implements Appendable, CharSequence {
//Appendable是抽象建造者
public interface Appendable {
Appendable append(CharSequence csq) throws IOException;
在jdk的java.lang.StringBuilder包中使用了建造者模式
四、结构型模式
5. 适配器模式
- 描述
- 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
- 将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼容
- 从用户的角度看不到被适配者,是解耦的
- 用户调用适配器转化出来的目标接口方法, 适配器再调用被适配者的相关接口方法
- 适配器模式属于结构型模式
- 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式
- 三种命名方式,是根据 BeAdapt 是以怎样的形式给到Adapter(在Adapter里的形式)来命名的。
- 类适配器:以类给到,在Adapter里,就是将BeAdapt 当做类,继承
- 对象适配器:以对象给到,在Adapter里,将BeAdapt 作为一个对象,持有
- 接口适配器:以接口给到,在Adapter里,将BeAdapt 作为一个接口,实现
- 实际开发中,实现起来不拘泥于以上的三种经典形式
5.1 类适配器模式
-
描述
Adapter类,通过继承 BeAdapt类,实现 InterfaceAdapt 接口,完成BeAdapt ->InterfaceAdapt 的适配
-
UML
//被适配类
class BeAdapt{
public int out220(){
return 220;
}
}
//目标适配接口
interface InterfaceAdapt{
int out5();
}
//适配器类
class Adapter extends BeAdapt implements InterfaceAdapt{
//转换适配
@Override
public int out5() {
return out220()/44;
}
}
//完成适配使用类
class use{
private InterfaceAdapt interfaceAdapt;
//注入InterfaceAdapt实现类对象实例
public void setAdapter(InterfaceAdapt interfaceAdapt){
this.interfaceAdapt=interfaceAdapt;
}
//使用适配方法
public void use(){
interfaceAdapt.out5();
}
}
- 优缺点说明
- Java是单继承机制,所以类适配器需要继承BeAdapt类这一点算是一个缺点, 因为这要求InterfaceAdapt必须是接口,有一定局限性
- BeAdapt类的方法在Adapter中都会暴露出来,也增加了使用的成本。
- 由于其继承了BeAdapt类,所以它可以根据需求重写BeAdapt类的方法,使得Adapter的灵活性增强了
5.2 对象适配器模式
-
描述
- 基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承BeAdapt类,而是持有BeAdapt类的实例,以解决兼容性的问题。 即:持有 BeAdapt类,实现 InterfaceAdapt 接口,完成 BeAdapt ->InterfaceAdapt 的适配
- 根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系。
- 对象适配器模式是适配器模式常用的一种
-
UML
//被适配类
class BeAdapt{
public int out220(){
return 220;
}
}
//目标适配接口
interface InterfaceAdapt{
int out5();
}
//适配器类
class Adapter implements InterfaceAdapt{
private BeAdapt beAdapt;
//注入BeAdapt对象实例
public void setBeAdapt(BeAdapt beAdapt){
this.beAdapt=beAdapt;
}
//转换适配
@Override
public int out5() {
return beAdapt.out220()/44;
}
}
//完成适配使用类
class use{
private InterfaceAdapt interfaceAdapt;
//注入InterfaceAdapt实现类对象实例
public void setAdapter(InterfaceAdapt interfaceAdapt){
this.interfaceAdapt=interfaceAdapt;
}
//使用适配方法
public void use(){
interfaceAdapt.out5();
}
}
-
优缺点说明
对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则, 使用组合替代继承, 所以它解决了类适配器必须继承BeAdapt的局限性问题,也不再要求InterfaceAdapt必须是接口。使用成本更低,更灵活。
5.3 接口适配器模式
-
描述
- 适配器模式(Default Adapter Pattern)或缺省适配器模式。
- 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
- 适用于一个接口不想使用其所有的方法的情况
-
UML
//需要适配的接口
interface Interface{
void fun1();
void fun2();
void fun3();
}
//接口适配抽象类,空方法方式实现接口中所有方法
abstract class AbsAdapter implements Interface{
@Override
public void fun1(){}
@Override
public void fun2(){}
@Override
public void fun3(){}
}
//匿名函数方式实现需要重写的方法
class use{
public void used(){
AbsAdapter absAdapter = new AbsAdapter(){
@Override
public void fun1() {
//根据业务重写fun1
}
};
absAdapter.fun1();
}
}
5.4 源码分析
public class DispatcherServlet extends FrameworkServlet {
// 通过HandlerMapping来映射Controller
mappedHandler = getHandler(processedRequest);
//获取适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//..
// 通过适配器调用controller的方法并返回ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
//DispatcherServlet的getHandlerAdapter方法,根据需要返回适当的HandlerAdapter
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException{
for(HandlerAdapter ha : this.handlerAdapters){
if(logger.isTraceEnabled()){
logger.trace("Testing handler adapter ["+ha+"]");
}
if(ha.supports(handler)){
return ha;
}
}
}
//HandlerAdapter是一个适配器接口,有多个实现类.
//使得每一种Controller有一种对应的适配器实现类,每种Controller有不同的实现方式
public interface HandlerAdapter{
boolean supports(Object handler);
//……
}
在SpringMVC的 HandlerAdapter 中使用了适配器模式
适配器模式在此处代替了if else这种判断。当DispatchServlet获取到不同的request时,可以通过不同的适配器获取不同的Controller的方法进行处理。当需要新增加一个Controller时,只需要增加一个适配器即可。
6. 桥接模式
6.1 桥接模式
-
描述
- 桥接模式(Bridge模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。
- 是一种结构型设计模式
- Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展
-
UML
//桥接类
abstract class Bridge{
private Interface interface1;
//注入Interface实现类的实例对象
public Bridge(Interface interface1){
this.interface1=interface1;
}
protected void fun(){
this.interface1.fun();
}
}
//接口
interface Interface{
void fun();
}
//Bridge的子类1
class RClass1 extends Bridge{
//使用父类构造器
public RClass1(Interface interface1){
super(interface1);
}
@Override
public void fun(){
super.fun();
//子类的逻辑
}
}
//Bridge的子类2
class RClass2 extends Bridge{
//使用父类构造器
public RClass2(Interface interface1){
super(interface1);
}
@Override
public void fun(){
super.fun();
//子类的逻辑
}
}
//接口实现类1
class IClass1 implements Interface{
@Override
public void fun() {
//业务逻辑
}
}
//接口实现类2
class IClass2 implements Interface{
@Override
public void fun() {
//业务逻辑
}
}
//use调用类
class use{
//让RClass和IClass分离,可以自由组合
public static void use1(Bridge bridge){
bridge.fun();
}
public static void main(String[] args) {
//当传入不同Bridge子类对象时可以调用不同的Interface实现类对象方法
use1(new RClass1(new IClass1()));
}
}
- 优缺点说明
- 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来, 这有助于系统进行分层设计,从而产生更好的结构化系统。
- 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成。
- 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
- 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性,即需要有这样的应用场景。
6.2 源码分析
//Jdbc 的 java.sql.Driver接口,如果从桥接模式来看,
//Driver就是一个接口,下面可以有MySQL的Driver, Oracle的Driver,这些就可以当做实现接口类
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
//1.注册驱动
//2. 调用DriverManager中的getConnection
//可以通过不同的Driver获取不同的Connection
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}..
public class DriverManager{
//...
@CallerSensitive
public static Connection getConnection(String url,java.util.Properties info) throws SQLException{
return (getConnection(url,info,Reflection.getCallerClass))
}
}
//java.sql.Connection接口下面还有com.mysql.jdbc.Connection子接口,
//再下面还有MySQLConnection子接口,然后才是实现类
public interface Connection extends Wrapper,AutoCloseabale{
}
在JDBC的通过DriverManager获取Connection源码中使用了桥接模式(变种)
6.2 桥接模式和适配器模式的区别
两种模式的区别在于使用场合的不同
- 适配器模式主要解决两个已经有接口间的匹配问题,这种情况下被适配的接口的实现往往是一个黑匣子。我们不想,也不能修改这个接口及其实现。同时也不可能控制其演化,只要相关的对象能与系统定义的接口协同工作即可。适配器模式经常被用在与第三方产品的功能集成上,采用该模式适应新类型的增加的方式是开发针对这个类型的适配器
- 桥接模式则不同,参与桥接的接口是稳定的,用户可以扩展和修改桥接中的类,但是不能改变接口。桥接模式通过接口继承实现或者类继承实现功能扩展
- 桥接模式和适配器模式用于设计的不同阶段。
- 桥接模式用于设计的前期,即在设计类时将类规划为逻辑和实现两个大类,是他们可以分别精心演化
- 而适配器模式用于设计完成之后,当发现设计完成的类无法协同工作时,可以采用适配器模式。然而很多情况下在设计初期就要考虑适配器模式的使用,如涉及到大量第三方应用接口的情况
7. 装饰模式★
7.1 装饰者模式
-
描述
装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)
-
UML
//被装饰的对象抽象类
abstract class Component {
public abstract void fun();
}
//被装饰的对象实现类
class ConcreteComponent extends Component {
@Override
public void fun() {
System.out.println("具体对象的操作");
}
}
//装饰抽象类,对被装饰的对象进行装饰
abstract class Decorator extends Component {
private Component component;
//注入被装饰对象
public Decorator(Component component) {
this.component = component;
}
//在被装饰对象上进行装饰
@Override
public void fun() {
this.component.fun();
}
}
//装饰抽象类的子类,在父类基础上进一步进行装饰
class ConcreteDecorator extends Decorator {
//注入被装饰对象
public ConcreteDecorator(Component component) {
super(component);
}
//定义子类自己的装饰方法
private void method() {
System.out.println("method 装饰");
}
//子类装饰过的方法
@Override
public void fun() {
this.method();
super.fun();
}
}
//使用
class use {
public static void main(String[] args) {
Component component = new ConcreteComponent();
//装饰
component = new ConcreteDecorator(component);
//装饰后运行
component.fun();
}
}
7.2 源码分享
public class FilterInputStream extends InputStream {
//被装饰者
protected volatile InputStream in;
//传入InputStream对象
protected FilterInputStream(InputStream in) {
this.in = in;
}
//使用InputStream的read方法
public int read() throws IOException {
return in.read();
}
//使用自己的装饰方法装饰InputStream原本的read(b, off, len)方法
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
return in.read(b, off, len);
}
jdk的java.io.FilterInputStream就是一个装饰者
8. 组合模式
8.1 组合模式
- 描述
- 组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系。
- 组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
- 这种类型的设计模式属于结构型模式。
- 组合模式使得用户对单个对象和组合对象的访问具有一致性, 即:组合能让客户以一致的方式处理个别对象以及组合对象
- UML
//树形结构的抽象类或接口
abstract class Component{
void add(Component component){};
void remove(){
throw new UnsupportedOperationException("不支持的操作");
};
void get(){
throw new UnsupportedOperationException("不支持的操作");
};
void fun(){
//逻辑
}
}
//树枝节点,继承Component,并且重写操作和读取方法
class Composite extends Component{
private List<Component> list=new ArrayList<>();
@Override
void add(Component component) {
list.add(component);
}
@Override
void remove() {
//remove
}
@Override
void get() {
//get
}
}
//叶子节点,继承Component并重写读取方法
class Leaf extends Component{
@Override
void get() {
//get
}
}
- 优缺点说明
- 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。
- 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动.
- 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构
- 需要遍历组织机构,或者处理的对象具有树形结构时, 非常适合使用组合模式.
- 要求较高的抽象性, 如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样, 不适合使用组合模式
8.2 源码分析
//Map接口
public interface Map<K,V> {
//AbstractMap相当于树形结构抽象层
public abstract class AbstractMap<K,V> implements Map<K,V> {
//HashMap相当于树枝节点
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
private static final long serialVersionUID = 362498820763181265L;
//重写父类的添加方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
//静态内部类Node相当于叶子节点
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
在jdk的java.util.HashMap中使用了组合模式
9. 外观模式
9.1 外观模式
- 描述
- 外观模式(Facade),也叫过程模式:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
- 外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节
- UML
//子系统1
enum ChildSys1{
INSTANCE;
public void sys1fun1(){ }
public void sys1fun2(){ }
public void sys1fun3(){ }
}
//子系统2
enum ChildSys2{
INSTANCE;
public void sys2fun1(){ }
public void sys2fun2(){ }
public void sys2fun3(){ }
}
//子系统3
enum ChildSys3{
INSTANCE;
public void sys3fun1(){ }
public void sys3fun2(){ }
public void sys3fun3(){ }
}
//外观模式类,聚合所有子系统的实例对象然后按照既定流程统一操作
class Facade{
private ChildSys1 sys1;
private ChildSys2 sys2;
private ChildSys3 sys3;
//获取所有子系统对象实例。在Facade构造方法中创建子系统实例是组合方式,也可以用聚合的方式
public Facade(){
this.sys1=ChildSys1.INSTANCE;
this.sys2=ChildSys2.INSTANCE;
this.sys3=ChildSys3.INSTANCE;
}
//流程1
public void fun1(){
sys1.sys1fun1();
sys2.sys2fun1();
sys3.sys3fun1();
}
//流程2
public void fun2(){
sys1.sys1fun2();
sys2.sys2fun2();
sys3.sys3fun2();
}
//流程3
public void fun3(){
sys1.sys1fun3();
sys2.sys2fun3();
sys3.sys3fun3();
}
}
class use{
public static void main(String[] args) {
Facade facade = new Facade();
//统一调用
facade.fun1();
facade.fun2();
facade.fun3();
}
}
- 优缺点说明
- 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
- 外观模式对客户端与子系统的耦合关系进行了解耦,让子系统内部的模块更易维护和扩展
- 通过合理的使用外观模式,可以帮我们更好的划分访问的层次
- 当系统需要进行分层设计时, 可以考虑使用Facade模式
- 在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口,让新系统与Facade类交互, 提高复用性
- 不能过多的或者不合理的使用外观模式,使用外观模式好, 还是直接调用模块好。要以让系统有层次,利于维护为目的。
9.2 源码分析
public class Configuration {
protected ReflectorFactory reflectorFactory =new DefaultReflectorFactory();
protected ObjectFactory objectFactory =new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory =new DefaultObjectWrapperFactory();
public MetaObject newMetaObject(Object object) {
//MetaObject相当于是外观模式聚合object,objectFactory,
//objectWrapperFactory,reflectorFactory等实例
return MetaObject.forObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
}
}
//以聚合的方式获取各个Factory的对象,根据Object的类型选择使用哪个Factory
public class MetaObject{
private MetaObject(Object object, ObjectFactory objectFactory,
ObjectWrapperFactory objectWrapperFactory,ReflectorFactory reflectorFactory){
this.originalObject = object;
this.objectFactory = objectFactory;
this.objectWrapperFactory = objectWrapperFactory;
this.reflectorFactory = reflectorFactory;
if (object instanceof ObjectWrapper) {
this.objectWrapper = (ObjectWrapper) object;
} else if (objectWrapperFactory.hasWrapperFor(object)) {
this.objectWrapper = objectWrapperFacto
} else if (object instanceof Map) {
this.objectWrapper = new MapWrapper(this, (Map) object);
} else if (object instanceof Collection) {
this.objectWrapper = new CollectionWrappe
} else {
this.objectWrapper = new BeanWrapper(this, object);
}
}
MyBatis 中的Configuration 去创建MetaObject 对象使用到外观模式
10. 享元模式
10.1 享元模式★
- 描述
- 享元模式(Flyweight Pattern) 也叫 蝇量模式: 运用共享技术有效地支持大量细粒度的对象
- 常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在
这些连接对象中有我们需要的则直接拿来用,避免重新创建, 如果没有我们需要的,则创建一个 - 享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。 不需
总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率 - 享元模式经典的应用场景就是池技术了, String常量池、数据库连接池、缓冲池等等都是享元模式
的应用,享元模式是池技术的重要实现方式 - 内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变
- 外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态
- UML
//需要享元的抽象类,也可以使用接口抽象
//共享内部状态,聚合外部状态
abstract class AbsClass{
abstract void fun(Outside outside);
}
//实现类
class RealClass extends AbsClass{
@Override
void fun(Outside outside) {
outside.out();
//逻辑
}
}
//AbsClass工厂类
class AbsClassFactory{
private static List<AbsClass> list=new ArrayList<>();
//如果有AbsClass对象实例则返回,没有则创建
public static AbsClass getAbsClass(){
if (list.isEmpty()){
list.add(new RealClass());
}
return list.get(0);
}
}
//外部状态
class Outside{
public void out(){}
}
//调用类
class use{
public static void main(String[] args) {
AbsClass absClass = AbsClassFactory.getAbsClass();
absClass.fun(new Outside());
}
}
- 优缺点说明
- 在享元模式这样理解,“享”就表示共享,“元”表示对象
- 系统中有大量对象, 这些对象消耗大量内存, 并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式
- 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储
- 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
- 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方.
- 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
- 享元模式经典的应用场景是需要缓冲池的场景,比如 String常量池、 数据库连接池
10.2 源码分析
public final class Integer extends Number implements Comparable<Integer> {
public static Integer valueOf(int i) {
//如果-128<=i<=127之间,则在IntegerCache的缓存中获取值(享元模式返回)
//如果不在此区间,则new一个新的Integer对象
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
在jdk的java.lang.Integer中的valueOf() 方法使用了享元模式
11. 代理模式★
- 描述
- 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。 即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
- 被代理的对象可以是远程对象、 创建开销大的对象或需要安全控制的对象
- 代理模式有不同的形式, 主要有三种 静态代理、 动态代理 (JDK代理、接口代理)和 Cglib代理 (可以在内存动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴) 。
11.1 静态代理
-
描述
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类
-
UML
//公共接口
interface IProxy{
void fun();
}
//被代理类
class TargetClass implements IProxy{
@Override
public void fun(){
//逻辑
}
}
//代理类,共同实现了IProxy,聚合了IProxy的实现类实例
class ProxyClass implements IProxy{
private IProxy iProxy;
public ProxyClass(IProxy iProxy){
this.iProxy=iProxy;
}
@Override
public void fun(){
//代理的逻辑
iProxy.fun();
}
}
//调用
class use{
public static void main(String[] args) {
//用被代理对象获取静态代理对象,调用代理对象的方法
IProxy proxyClass = new ProxyClass(new TargetClass());
proxyClass.fun();
}
}
- 优缺点说明
- 优点:在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展
- 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类
- 一旦接口增加方法,目标对象与代理对象都要维护
- 静态代理模式和装饰模式的对比★
- 装饰对象与被装饰对象都实现同样的接口,代理对象和被代理对象也同样需要实现同样的接口。
- 装饰起到方法增强效果,代理不仅起到方法增强效果,还起到权限控制效果。
- 装饰设计模式是静态代理的一种。
11.2 动态代理
- 描述
- 代理对象,不需要实现接口, 但是目标对象要实现接口, 否则不能用动态代理
- 代理对象的生成, 是利用JDK的API,动态的在内存中构建代理对象
- 动态代理也叫做: JDK代理、 接口代理
- 代理类所在包:java.lang.reflect.Proxy
- JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完
整的写法是
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
- UML
//目标对象实现接口
interface IProxy{
void fun();
}
//被代理类
class TargetClass implements IProxy{
@Override
public void fun(){
//逻辑
}
}
//代理工厂,聚合了IProxy的实现类实例
class ProxyFactory{
private Object target;
public ProxyFactory(Object target){
this.target=target;
}
public Object getProxyInstance(){
//1.ClassLoader loader:指定当前目标对象使用的类加载器,方法固定
//2.Class<?>[] interfaces:目标对象实现的接口类型,使用泛型方法确认类型
//3.InvocationHandler h:事件处理,会触发事件处理器方法,会把当前执行的目标对象方法作为参数传入
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//invoke的第一个参数obj:调用方法的对象,一般都是target
//invoke的第二个参数args:方法的参数,一般都是args
//注意:此处不要使用proxy代理对象当方法调用的本体,这样会形成递归死循环
//代理对象逻辑...
Object returnVal = method.invoke(target, args);
//代理对象逻辑...
return returnVal;
}
});
}
}
//调用
class use{
public static void main(String[] args) {
ProxyFactory proxyClass = new ProxyFactory(new TargetClass());
//获取TargetClass的动态代理对象
IProxy proxyInstance =(IProxy)proxyClass.getProxyInstance();
proxyInstance.fun();
}
}
11.3 Cglib代理
-
描述
- 静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是Cglib代理
- Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将Cglib代理归属到动态代理。
- Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP, 实现方法拦截
- Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类
- 在AOP编程中如何选择代理模式:
- 目标对象需要实现接口, 用JDK代理
- 目标对象不需要实现接口, 用Cglib代理
-
注意事项
- 需要引入cglib的jar文件 cglib-nodep-2.2.jar(包含cglib-2.2.jar,asm-tree.jar,asm-commons.jar,asm.jar)
- 内存中动态构建子类, 注意代理的类不能为final,否则报错java.lang.IllegalArgumentException
- 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
-
UML
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
//被代理类
class TargetClass{
public void fun(){
//逻辑
}
}
//代理工厂,聚合了IProxy的实现类实例
class ProxyFactory implements MethodInterceptor {
private Object target;
public ProxyFactory(Object target){
this.target=target;
}
//返回一个target的代理对象
public Object getProxyInstance(){
//1.创建一个工具类
Enhancer enhancer = new Enhancer();
//2.设置父类
enhancer.setSuperclass(target.getClass());
//3.设置回调函数
enhancer.setCallback(this);
//4.创建子类对象,即代理对象
return enhancer.create();
}
//重写intercept,调用目标对象的方法
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//代理对象逻辑...
Object returnVal = method.invoke(target, objects);
//代理对象逻辑...
return returnVal;
}
}
//调用
class use{
public static void main(String[] args) {
ProxyFactory proxyClass = new ProxyFactory(new TargetClass());
//获取TargetClass的Cglib代理对象
TargetClass proxyInstance =(TargetClass)proxyClass.getProxyInstance();
proxyInstance.fun();
}
}
11.4 代理模式的几种变体
- 防火墙代理:内网通过代理穿透防火墙,实现对公网的访问。
- 缓存代理:比如当请求图片文件等资源时,先到缓存代理取,如果取到资源则ok,如果取不到资源,再到公网或者数据库取,然后缓存。
- 远程代理:远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息
- 同步代理:主要使用在多线程编程中,完成多线程间同步工作
五、行为型模式
12. 模板方法模式
12.1 模板方法模式
- 描述
- 模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
- 简单说, 模板方法模式 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤
- 这种类型的设计模式属于行为型模式。
- UML
//模板抽象类,实现了模板template方法,定义了算法骨架,子类实现具体方法fun1,2,3
//模板方法可以定义为final,不让子类重写
abstract class AbsClass{
public final void template(){
fun1();
//通过钩子方法来控制改流程是否执行
if(hook()){
fun3();
}
fun2();
}
//可以是抽象方法也可以是实现的方法
abstract void fun1();
abstract void fun2();
abstract void fun3();
//钩子方法
public boolean hook(){
return true;
}
}
//子类实现模板类中的抽象方法
class RealClass extends AbsClass{
@Override
void fun1() {
//实现逻辑
}
@Override
void fun2() {
//实现逻辑
}
@Override
void fun3() {
//实现逻辑
}
//子类可以根据需求选择是否重写该方法,控制fun2是否执行
@Override
public boolean hook() {
return false;
}
}
//调用模板方法。不同实现类的模板方法结果可能不同,但是大体执行流程是固定的
class use{
public static void main(String[] args) {
AbsClass realClass = new RealClass();
realClass.template();
}
}
- 优缺点说明
- 基本思想是: 算法只存在于一个地方,也就是在父类中,容易修改。 需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
- 实现了最大化代码复用。 父类的模板方法和已实现的某些步骤会被子类继承而直接使用。
- 既统一了算法,也提供了很大的灵活性。 父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。
- 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
- 一般模板方法都加上final关键字, 防止子类重写模板方法.
- 模板方法模式使用场景: 当要完成在某个过程, 该过程要执行一系列步骤 , 这一系列的步骤基本相同,但其个别步骤在实现时可能不同,通常考虑用模板方法模式来处理
- 在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。
12.2 源码分析
//模板接口
public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {
//声明了一个模板方法
void refresh() throws BeansException,IllegalStateException;
}
//实现了模板方法的抽象类
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
//模板方法的实现
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
//该方法在下方执行两个抽象方法
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
this.refreshBeanFactory();
return this.getBeanFactory();
}
//抽象方法
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
//抽象方法
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
//钩子方法
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}
//实现抽象模板中的抽象方法
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
//实现抽象方法
protected final void refreshBeanFactory() throws IllegalStateException {
if (!this.refreshed.compareAndSet(false, true)) {
throw new IllegalStateException("GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
} else {
this.beanFactory.setSerializationId(this.getId());
}
}
//实现抽象方法
public final ConfigurableListableBeanFactory getBeanFactory() {
return this.beanFactory;
}
Spring IOC容器初始化时运用到的模板方法模式
13. 命令模式
13.1 命令模式
- 描述
- 命令模式(Command Pattern): 在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计
- 命名模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
- 在命名模式中,会将一个请求封装为一个对象, 以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作。
- 通俗的理解:将军发布命令,士兵去执行。其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。Invoker是调用者(将军), Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,持有接收对象
- UML
//命令接口,需要执行的命令都在这里,可以是接口或抽象类
interface Command{
//执行操作
void execute();
//撤销操作
void undo();
}
//命令实现类,将一个接受者对象与一个动作绑定,调用接受者相应的操作,实现execute
//一般一个实现类只代表一个操作,比如当前类表示开灯操作
class ConcreteCommand implements Command{
private Receiver receiver;
//注入接受者对象
public ConcreteCommand(Receiver receiver){
this.receiver=receiver;
}
//调用接受者对象的方法完成操作
//对开灯操作execute是on, undo是off
//如果还有一个关灯实现类,则execute是off, undo是on
@Override
public void execute() {
receiver.on();
}
@Override
public void undo() {
receiver.off();
}
}
//空命令对于简化操作有帮助,省去对空的判断
class NoCommand implements Command{
@Override
public void execute() {}
@Override
public void undo() {}
}
//接受者对象,比方说电灯,有打开和关闭
class Receiver{
public void on(){
//打开
}
public void off(){
//关闭
}
}
//调用者对象
class Invoker{
//用一个集合接收命令,此处用数组
private Command[] onCommands=new Command[1];
//撤销命令
private Command undoCommand=new NoCommand();
//初始化开关命令集合为空命令
public Invoker() {
onCommands[0]=new NoCommand();
}
//设置命令方法
public void setOnCommands(int no,Command command){
onCommands[no]=command;
}
//执行方法
public void run(int no){
onCommands[no].execute();
//记录当前的操作对象
undoCommand=onCommands[no];
}
//撤销方法
public void undo(){
undoCommand.undo();
}
}
//使用
class use{
public static void main(String[] args) {
//创建操作对象,把接收者注入
ConcreteCommand concreteCommand = new ConcreteCommand(new Receiver());
Invoker invoker = new Invoker();
//把操作设置到Invoker中
invoker.setOnCommands(0,concreteCommand);
//操作invoker
invoker.run(0);
invoker.undo();
}
}
- 优缺点说明
- 将发起请求的对象与执行请求的对象进行解耦。发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:“请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。
- 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令
- 容易实现对请求的撤销和重做
- 命令模式不足:可能导致某些系统有过多的具体命令类, 增加了系统的复杂度, 这点在在使用的时候要注意
- 空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。
- 命令模式经典的应用场景:界面的一个按钮都是一条命令、 模拟CMD(DOS命令)订单的撤销/恢复、触发-反馈机制
13.2 源码分析
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
//jdbcTemplate作为Invoker
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
return (List)result(this.query((String)sql, (ResultSetExtractor)(new RowMapperResultSetExtractor(rowMapper))));
}
@Nullable
public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
Assert.notNull(sql, "SQL must not be null");
Assert.notNull(rse, "ResultSetExtractor must not be null");
if (this.logger.isDebugEnabled()) {
this.logger.debug("Executing SQL query [" + sql + "]");
}
//此内部类实现了StatementCallback(类似Command),相当于ConcreteCommand,同时也相当于Receiver
//doInStatement被重写,不同的StatementCallback实现类对应着不同的doInStatement方法实现
//jdbcTemplate作为Invoker,execute(StatementCallback<T> action) 调用action.doInStatement 方法
class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
QueryStatementCallback() {
}
@Nullable
public T doInStatement(Statement stmt) throws SQLException {
ResultSet rs = null;
Object var3;
try {
rs = stmt.executeQuery(sql);
var3 = rse.extractData(rs);
} finally {
JdbcUtils.closeResultSet(rs);
}
return var3;
}
public String getSql() {
return sql;
}
}
//execute执行把内部类传入
return this.execute((StatementCallback)(new QueryStatementCallback()));
}
//类似于Command接口
@FunctionalInterface
public interface StatementCallback<T> {
@Nullable
T doInStatement(Statement var1) throws SQLException, DataAccessException;
}
Spring框架的JdbcTemplate就使用到了命令模式
14. 访问者模式
14.1 访问者模式
-
描述
- 访问者模式(Visitor Pattern),封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
- 主要将数据结构与数据操作分离,解决数据结构和操作耦合性问题
- 访问者模式的基本工作原理是: 在被访问的类里面加一个对外提供接待访问者的接口
- 访问者模式主要应用场景是: 需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),同时需要避免让这些操作"污染"这些对象的类,可以选用访问者模式解决
- 所谓双分派是指不管类怎么变化,我们都能找到期望的方法运行。双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型
-
UML
//访问者接口
interface Visitor{
//访问者中要传入ConcreteElement对象
//可以通过传入不同的Element实现对象进行重载
/**
*它定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,
*它的方法个数理论上来讲与元素个数(Element的实现类个数)是一样的,从这点不难看出,
*访问者模式要求元素类的个数不能改变(不能改变的意思是说,如果元素类的个数经常改变,
*则说明不适合使用访问者模式)
*/
void fun(ConcreteElement element);
}
//访问者实现类
class ConcreteVisitor implements Visitor{
@Override
public void fun(ConcreteElement element) {
}
}
//接收接口
interface Element{
//接收一个Visitor对象
void accept(Visitor visitor);
}
//接收实现类
//双分派:
// 1.在use中把Visitor作为参数传递到ConcreteElement中(第一次分派)
// 2.然后ConcreteElement调用作为参数的Visitor中的fun方法,同时将自己(this)对象作为参数传入(第二次分派)
class ConcreteElement implements Element{
@Override
public void accept(Visitor visitor) {
//Visitor中需要传入具体实现类是因为可以通过不同的Element实现对象调用不同的方法
visitor.fun(this);
}
}
//数据结构,管理双方对象,用来允许访问者访问元素
class ObjectStruture{
//维护一个集合
private List<Element> elementList=new LinkedList<>();
//添加
public void add(Element element){
elementList.add(element);
}
//移除
public void remove(Element element){
elementList.remove(element);
}
//显示当前visitor和所有element的情况
public void show(Visitor visitor){
for (Element element : elementList) {
element.accept(visitor);
}
}
}
//调用
class use{
public static void main(String[] args) {
//先创建ObjectStruture对象
ObjectStruture objectStruture = new ObjectStruture();
//往ObjectStruture中添加ConcreteElement对象
objectStruture.add(new ConcreteElement());
//获取ConcreteVisitor访问的结果
objectStruture.show(new ConcreteVisitor());
}
}
- 优缺点说明
- 访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高
- 访问者模式可以对功能进行统一,可以做报表、 UI、拦截器与过滤器,适用于数据结构相对稳定的系统
- 具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的, 这样造成了具体元素变更比较困难
- 违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素
- 因此,如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的
15. 迭代器模式
15.1 迭代器模式
- 描述
- 迭代器模式(Iterator Pattern) 是常用的设计模式,属于行为型模式
- 如果我们的集合元素是用不同的方式实现的, 有数组,还有java的集合类,或者还有其他方式,当客户端要遍历这些集合元素的时候就要使用多种遍历方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。
- 迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素,不需要知道集合对象的底层表示,即: 不暴露其内部的结构。
- UML
//元素类
class Element{
private int id;
public Element(int id){
this.id=id;
}
public int getId(){
return id;
}
}
//Iterator实现类,Iterator是系统接口。
class ConcreteIterator implements Iterator<Element>{
//这里需要知道Element是以什么形式存放的
private Element[] elements;
int position=0;//遍历位置
public ConcreteIterator(Element[] elements) {
this.elements = elements;
}
//判断是否还有下一个
@Override
public boolean hasNext() {
if(elements[position]==null || position>=elements.length){
return false;
}
return true;
}
//获取下一个Element
@Override
public Element next() {
Element element=null;
if (elements!=null && position<elements.length){
element = elements[this.position++];
}
return element;
}
}
//元素集合抽象类
interface AbsClass{
Iterator getIterator();
}
//元素集合实现类
class ConcreteClass implements AbsClass{
private Element[] elements=new Element[3];
public ConcreteClass(){
elements[0]=new Element(1);
elements[0]=new Element(2);
elements[0]=new Element(3);
}
@Override
public Iterator<Element> getIterator() {
//根据不同的集合存储数据的方式,创建不同的ConcreteIterator来遍历
return new ConcreteIterator(elements);
}
}
//调用
class use{
public static void main(String[] args) {
ConcreteClass concreteClass = new ConcreteClass();
Iterator<Element> iterator = concreteClass.getIterator();
while (iterator.hasNext()){
Element element = iterator.next();
element.getId();
}
}
}
- 优缺点说明
- 提供一个统一的方法遍历对象,客户不用再考虑聚合的类型,使用一种方法就可以遍历对象了。
- 隐藏了聚合的内部结构,客户端要遍历聚合的时候只能取到迭代器,而不会知道聚合的具体组成。
- 提供了一种设计思想,就是一个类应该只有一个引起变化的原因(叫做单一责任原则)。在聚合类中,我们把迭代器分开,就是要把管理对象集合和遍历对象集合的责任分开,这样一来集合改变的话,只影响到聚合对象。而如果遍历方式改变的话,只影响到了迭代器。
- 当要展示一组相似对象,或者遍历一组相同对象时使用, 适合使用迭代器模式
- 每个聚合对象都要一个迭代器,会生成多个迭代器不好管理类
15.2 源码分析
//ArrayList实现List接口,List相当于AbsClass,ArrayList相当于ConcreteClass
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//相当于getIterator方法,获取Iterator对象
public Iterator<E> iterator() {
return new Itr();
}
//用内部类实现Iterator接口,相当于ConcreteIterator,
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
//因为Itr是内部类,所以直接使用elementData
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
JDK的ArrayList 集合中就使用了迭代器模式
16. 观察者模式★
16.1 观察者模式
-
描述
观察者模式:对象之间多对一依赖的一种设计方案,被依赖的对象为Subject,依赖的对象为Observer, Subject通知Observer变化。Subject是1的一方,Observer是多的一方
-
UML
//依赖对象接口
interface Subject{
void register(Observer obs);
void remove(Observer obs);
void notifyOb();
}
//依赖对象实现类
class ConceteSubject implements Subject{
private int param;
private List<Observer> list;
public void setParam(int param){
this.param=param;
//当内容有变化时推送给所有观察者
notifyOb();
}
public ConceteSubject() {
this.list=new ArrayList<>();
}
@Override
public void register(Observer obs) {
list.add(obs);
}
@Override
public void remove(Observer obs) {
list.remove(obs);
}
//通知所有观察者
@Override
public void notifyOb() {
for (Observer observer : list) {
observer.update(param);
}
}
}
//观察者接口
interface Observer{
void update(int param);
}
//观察者实现类
class ConceteObserver implements Observer{
private int param;
@Override
public void update(int param) {
this.param=param;
}
public int getParam(){
return param;
}
}
//调用
class use{
public static void main(String[] args) {
//创建一个依赖对象
ConceteSubject conceteSubject = new ConceteSubject();
//创建观察者对象
ConceteObserver conceteObserver1 = new ConceteObserver();
ConceteObserver conceteObserver2 = new ConceteObserver();
//注册观察者对象
conceteSubject.register(conceteObserver1);
conceteSubject.register(conceteObserver2);
//更新依赖对象中元素的时候就会实时更新所有观察者的元素
conceteSubject.setParam(1);
}
}
- 优缺点说明
- 观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除和通知。
- 这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类, 遵守了ocp原则
16.2 源码分析
//Observable没有抽象成接口,直接本类完成了Subject的功能
public class Observable {
private boolean changed = false;
private Vector<Observer> obs; //用于接收Observer的集合
public Observable() {
obs = new Vector<>();
}
//注册Observer
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
//删除Observer
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
//通知Observer
public void notifyObservers() {
notifyObservers(null);
}
//通知Observer
public void notifyObservers(Object arg) {
Object[] arrLocal;
//用同步代码块线程安全的判断数据是否有更新,如果有则通知,并且清除更新标记
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
public synchronized void deleteObservers() {
obs.removeAllElements();
}
protected synchronized void setChanged() {
changed = true;
}
protected synchronized void clearChanged() {
changed = false;
}
public synchronized boolean hasChanged() {
return changed;
}
public synchronized int countObservers() {
return obs.size();
}
}
Jdk的java.util.Observable类就使用了观察者模式
17. 中介者模式
17.1 中介者模式
- 描述
- 中介者模式(Mediator Pattern) , 用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
- 中介者模式属于行为型模式, 使代码易于维护比如MVC模式, C(Controller控制器)是M(Model模型)和V(View视图)的中介者,在前后端交互时起到了中间人的作用
- UML
//中介抽象类
abstract class Mediator{
//维护所有同事对象的集合
protected Map<String,Colleague> map;
public Mediator() {
this.map=new HashMap<>();
}
//把同事类添加到map中
public abstract void register(String name,Colleague colleague);
//接收同事实现类发出的消息
public abstract void getMessage(int stataChange, String name);
public abstract void sendMessage();
}
//中介实现类
class ConcreteMediator extends Mediator{
@Override
public void register(String name, Colleague colleague) {
map.put(name,colleague);
}
//中介者核心方法
@Override
public void getMessage(int stataChange, String name) {
Colleague colleague=null;
//根据得到的消息完成对应任务
//中介者在这个方法中协调各个具体的同事对象完成任务
switch (stataChange){
case 0:
colleague = map.get(name);
break;
case 1:
colleague = map.get(name+1);
break;
case 2:
colleague = map.get(name+2);
break;
default:
colleague = null;
}
}
@Override
public void sendMessage() {
}
}
//同事抽象类
abstract class Colleague{
private Mediator mediator;
protected String name;
public Colleague(Mediator mediator, String name) {
this.mediator = mediator;
this.name = name;
}
public Mediator getMediator() {
return this.mediator;
}
public abstract void sendMessage(int stateChange);
}
//同事实现类
class ConcreteColleague1 extends Colleague{
public ConcreteColleague1(Mediator mediator, String name) {
super(mediator, name);
//创建对象时将自己放到Mediator实现类对象中
mediator.register(name, this);
}
//给中介者发送消息
@Override
public void sendMessage(int stateChange) {
this.getMediator().getMessage(stateChange, this.name);
}
}
//同事实现类
class ConcreteColleague2 extends Colleague{
public ConcreteColleague2(Mediator mediator, String name) {
super(mediator, name);
//创建对象时将自己放到Mediator实现类对象中
mediator.register(name, this);
}
//给中介者发送消息
@Override
public void sendMessage(int stateChange) {
this.getMediator().getMessage(stateChange, this.name);
}
}
//调用
class use{
public static void main(String[] args) {
//创建中介者对象
Mediator mediator = new ConcreteMediator();
//创建同事对象并通过构造器把自己注册到中介对象中
ConcreteColleague1 colleague1 = new ConcreteColleague1(mediator, "Colleague1");
ConcreteColleague2 colleague2 = new ConcreteColleague2(mediator, "Colleague2");
//给中介对象发送消息0,中介对象会按照0号方案预设的流程去处理colleague1对象
colleague1.sendMessage(0);
}
}
- 优缺点说明
- 多个类相互耦合,会形成网状结构, 使用中介者模式将网状结构分离为星型结构,进行解耦
- 减少类间依赖,降低了耦合, 符合迪米特原则
- 中介者承担了较多的责任,一旦中介者出现了问题,整个系统就会受到影响
- 如果设计不当,中介者对象本身变得过于复杂, 这点在实际使用时,要特别注意
18. 备忘录模式
18.1 备忘录模式
- 描述
- 备忘录模式(Memento Pattern) 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态
- 可以这里理解备忘录模式:现实生活中的备忘录是用来记录某些要去做的事情,或者是记录已经达成的共同意见的事情,以防忘记了。而在软件层面,备忘录模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作
- 备忘录模式属于行为型模式
- UML
//原始信息类
class Origin{
//状态信息
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
//保存。返回保存对象
public Memery save(){
return new Memery(this.state);
}
//获取保存对象恢复状态信息
public void get(Memery memery){
this.state=memery.getState();
}
}
//保存对象
class Memery{
//状态信息
private String state;
public Memery(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
//保存对象管理类
class Caretaker{
//集合维护保存对象
private List<Memery> list=new ArrayList<>();
//添加保存对象到集合中管理
public void add(Memery memery){
list.add(memery);
}
//获取第index个保存对象
public Memery get(int index){
return list.get(index);
}
}
//调用
class use{
public static void main(String[] args) {
//获取原始信息类和保存对象管理类对象
Origin origin = new Origin();
Caretaker caretaker = new Caretaker();
//设置原始状态为100
origin.setState("100");
//保存状态
caretaker.add(origin.save());
//设置原始状态为80
origin.setState("80");
//把原始状态恢复为100
origin.get(caretaker.get(0));
}
}
- 优缺点说明
- 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态
- 实现了信息的封装,使得用户不需要关心状态的保存细节
- 如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存, 这个需要注意
- 为了节约内存,备忘录模式可以和原型模式配合使用
- 适用的应用场景:
- 后悔药
- 打游戏时的存档
- Windows 里的 ctri + z。
- IE 中的后退。
- 数据库的事务管理
19. 解释器模式
19.1 解释器模式
- 描述
- 在编译原理中, 一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器
- 解释器模式(Interpreter Pattern):是指给定一个语言(表达式), 定义它的文法的一种表示,并定义一个解释器, 使用该解释器来解释语言中的句子(表达式)
- 应用场景
- 应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
- 一些重复出现的问题可以用一种简单的语言来表达
- 一个简单语法需要解释的场景
- 这样的例子还有,比如编译器、 运算表达式计算、正则表达式、 机器人等
- UML
//抽象表达式, 声明一个抽象的解释操作,这个方法为抽象语法树中所有的节点所共享
abstract class AbsExpression{
//解释表达式计算数值
public abstract int interpret(Map<String,Integer> map);
}
//为终结符表达式, 实现与文法中的终结符相关的解释操作
//变量解释器
class TerminalExpression extends AbsExpression{
private String key;
public TerminalExpression(String key) {
this.key = key;
}
//根据变量名称返回对应值
@Override
public int interpret(Map<String, Integer> map) {
return map.get(key);
}
}
//为非终结符表达式,为文法中的非终结符实现解释操作
//运算符解释器
class NonTerminalExpression extends AbsExpression{
//每个运算符都只和自己左右两个数字有关系,但左右两个数字有可能也是一个解释结果。无论何种类型,都是AbsExpression的实现类
protected AbsExpression left;
protected AbsExpression right;
public NonTerminalExpression(AbsExpression left, AbsExpression right) {
this.left = left;
this.right = right;
}
//这个可以让其子类来实现,目前只需要一个默认实现(例如:加减乘除运算符,分别在子类实现)
@Override
public int interpret(Map<String, Integer> map) {
return 0;
}
}
//运算符解释器子类实现,加法解释器
class AddExpression extends NonTerminalExpression{
//调用父类构造初始化,把左边的值和右边的值初始
public AddExpression(AbsExpression left, AbsExpression right) {
super(left, right);
}
//由于left和right都是TerminalExpression,所以interpret会取出对应表达式的值
@Override
public int interpret(Map<String, Integer> map) {
return super.left.interpret(map)+super.right.interpret(map);
}
}
//表达式解析类
//环境角色,含有解释器之外的全局信息
class Context {
//定义表达式
private AbsExpression absExpression;
public Context(String expStr){ //a+b
//创建一个栈安排运算先后顺序
Stack<AbsExpression> stack=new Stack<>();
//表达式拆分成字符数组
char[] charArray=expStr.toCharArray();//[a,+,b]
AbsExpression left=null;
AbsExpression right=null;
// 遍历字符数组
//针对不同运算符分别处理
for (int i = 0; i < charArray.length; i++) {
switch (charArray[i]){
case '+':
//如果对象是一个运算符,则从栈中取出最左边(第一个)元素为left,再从数组中取出下一个元素为right
//把他们包装成AddExpression并压入stack中
left=stack.pop(); //从栈中取出left
right=new TerminalExpression(String.valueOf(charArray[++i]));//i+1,从数组中取出下一个元素right
stack.push(new AddExpression(left, right));//根据得到的left和right创建AddExpression并压入栈中
break;
default:
//如果字符是一个元素,则包装成一个TerminalExpression对象并push到stack
stack.push(new TerminalExpression(String.valueOf(charArray[i])));
break;
}
}
//当遍历完整个charArray数组后,从stack就得到最后的AddExpression存入absExpression属性中
this.absExpression=stack.pop();
}
public int run(Map<String,Integer> map){
//absExpression里是AddExpression对象,所以执行AddExpression的interpret方法,把left和right对应的值取出来相加
return this.absExpression.interpret(map);
}
}
//调用
class use{
public static void main(String[] args) throws IOException {
String expStr=getExpStr(); //a+b
Map<String, Integer> map=getValue(expStr);//map {a=10,b=20}
Context context =new Context(expStr);
System.out.println("结果:"+expStr + "="+ context.run(map));
}
//开启键盘输入,获得表达式
public static String getExpStr() throws IOException{
System.out.println("输入表达式");
return (new BufferedReader(new InputStreamReader(System.in))).readLine();
}
//获得值映射
public static Map<String, Integer> getValue(String expStr) throws IOException{
Map<String, Integer> map=new HashMap<>();
for (char ch : expStr.toCharArray()) {
if (ch != '+'){
if (!map.containsKey(String.valueOf(ch))) {
System.out.print("请输入" + String.valueOf(ch) + "的值:");
//分别输入key=a和key=b的值,并把键值对保存在map中
String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();
map.put(String.valueOf(ch), Integer.valueOf(in));
}
}
}
return map;
}
}
- 优缺点说明
- 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性
- 应用场景:编译器、运算表达式计算、正则表达式、机器人等
- 使用解释器可能带来的问题: 解释器模式会引起类膨胀、 解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能降低
19.2 源码分析
//相当于AbsExpression抽象类
public interface ExpressionParser {
Expression parseExpression(String var1) throws ParseException;
Expression parseExpression(String var1, ParserContext var2) throws ParseException;
}
public abstract class TemplateAwareExpressionParser implements ExpressionParser {
public TemplateAwareExpressionParser() {
}
public Expression parseExpression(String expressionString) throws ParseException {
return this.parseExpression(expressionString, (ParserContext)null);
}
//根据传入的表达式不同返回不同的Expression对象
private Expression parseTemplate(String expressionString, ParserContext context) throws ParseException {
if (expressionString.isEmpty()) {
return new LiteralExpression("");
} else {
Expression[] expressions = this.parseExpressions(expressionString, context);
return (Expression)(expressions.length == 1 ? expressions[0] : new CompositeStringExpression(expressionString, expressions));
}
}
//相当于Context,使用时先获取SpelExpressionParser对象,然后调用TemplateAwareExpressionParser中实现的
//parseExpression方法,传入一个表达式字符串,包装成表达式对象并解释执行,parseExpression相当于run
public class SpelExpressionParser extends TemplateAwareExpressionParser {
private final SpelParserConfiguration configuration;
public SpelExpressionParser() {
this.configuration = new SpelParserConfiguration();
}
public SpelExpressionParser(SpelParserConfiguration configuration) {
Assert.notNull(configuration, "SpelParserConfiguration must not be null");
this.configuration = configuration;
}
public SpelExpression parseRaw(String expressionString) throws ParseException {
return this.doParseExpression(expressionString, (ParserContext)null);
}
protected SpelExpression doParseExpression(String expressionString, @Nullable ParserContext context) throws ParseException {
return (new InternalSpelExpressionParser(this.configuration)).doParseExpression(expressionString, context);
}
}
Spring框架中 org.springframework.expression.spel.standard.SpelExpressionParser就使用到解释器模式
20. 状态模式
20.1 状态模式
- 描述
- 状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换
- 当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类
- UML
//状态抽象类
abstract class State{
public abstract void fun();
}
//状态子类A,代表其中一种状态
class ConcreteStateA extends State{
private Context context;
public ConcreteStateA() {
}
//注入Context对象
public ConcreteStateA(Context context) {
this.context = context;
}
@Override
public void fun() {
//在状态对象的行为方法中改变状态
context.setState(new ConcreteStateB());
}
}
//状态子类B,代表其中一种状态
class ConcreteStateB extends State{
private Context context;
public ConcreteStateB() {
}
public ConcreteStateB(Context context) {
this.context = context;
}
@Override
public void fun() {
context.setState(new ConcreteStateA());
}
}
//状态上下文对象
class Context{
private State state;
//持有所有状态子类对象,把自己对象传入
State stateA=new ConcreteStateA(this);
State stateB=new ConcreteStateB(this);
//初始化设置默认状态
public Context() {
this.state = stateA;
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
//外部使用上下文对象的行为方法时,调用当前状态对象的行为方法
public void fun(){
state.fun();
}
}
//调用
class use{
public static void main(String[] args) {
Context context = new Context();
context.fun();
}
}
- 优缺点说明
- 代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中
- 方便维护。将容易产生问题的if-else语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多if-else语句,而且容易出错
- 符合“开闭原则”。容易增删状态
- 会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度
- 应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式
21. 策略模式
21.1 策略模式
- 描述
- 策略模式(Strategy Pattern)中,定义算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
- 这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口);第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)
- UML
//策略接口,代码模块中需要动态配置的部分抽取
interface Interface{
void fun();
}
//策略接口实现类A,代表策略的一种
class ConcreteInterfaceA implements Interface{
@Override
public void fun() {
//该策略逻辑
}
}
//策略接口实现类B,代表策略的一种
class ConcreteInterfaceB implements Interface{
@Override
public void fun() {
//该策略逻辑
}
}
//抽象整合类。代码模块中共用的部分
abstract class Context{
protected Interface interface1;
public void fun(){
//父类默认实现方法
}
}
//整合实现类
class ConcreteContext extends Context{
public ConcreteContext() {
//在构造器中可以动态的指定使用策略A还是策略B,把以前放在父类中的代码抽取出来以聚合或
//组合的方式动态的配置给其他类
interface1=new ConcreteInterfaceA();
}
//子类自己的方法调用父类方法和策略对象方法
public void fun1(){
super.fun();
interface1.fun();
}
}
//调用
class use{
public static void main(String[] args) {
ConcreteContext concreteContext = new ConcreteContext();
concreteContext.fun1();
}
}
- 优缺点说明
- 策略模式的关键是: 分析项目中变化部分与不变部分
- 策略模式的核心思想是:多用组合/聚合 少用继承;用行为类组合,而不是行为的继承。更有弹性
- 体现了开闭原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可, 避免了使用多重转移语句(if…else if…else)
- 提供了可以替换继承关系的办法: 策略模式将算法封装在独立的Interface类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展
- 需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大
21.2 源码分析
//相当于Interface策略接口。
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
//当使用时,实现Comparator接口的对象就是策略实现对象
Comparator comparator= new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return 0;
}
@Override
public boolean equals(Object obj) {
return false;
}
};
//Arrays相当于Context整合类
Arrays.sort(new Integer[]{1,2,3}, comparator);
//Arrays相当于Context整合类。通过改变策略实现类来实现排序的方式
public class Arrays {
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a);
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
JDK的 Arrays 的 java.util.Comparator 就使用了策略模式
22. 职责链模式
22.1 职责链模式
- 描述
- 职责链模式(Chain of Responsibility Pattern) ,又叫 责任链模式,为请求创建了一个接收者
对象的链。 这种模式对请求的发送者和接收者进行解耦。 - 职责链模式通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,
那么它会把相同的请求传给下一个接收者,依此类推。 - 这种类型的设计模式属于行为型模式
- 职责链模式(Chain of Responsibility Pattern) ,又叫 责任链模式,为请求创建了一个接收者
- UML
//请求对象
class Request{
private int param1;
private int param2;
public Request(int param1, int param2) {
this.param1 = param1;
this.param2 = param2;
}
public int getParam1() {
return param1;
}
public int getParam2() {
return param2;
}
}
//抽象的处理者, 定义了一个处理请求的接口, 同时含义另外Handler
abstract class Handler{
protected Handler handler;
private String name;
public Handler(String name) {
this.name = name;
}
//设置下一个处理着
public void setHandler(Handler handler) {
this.handler = handler;
}
//处理请求的方法,子类实现
public abstract void processRequest(Request request);
}
//具体的处理者A, 处理它自己负责的请求, 可以访问它的后继者(即下一个处
//理者), 如果可以处理当前请求,则处理,否则就将该请求交个 后继者去处理,从而形成一个职责链
class ConcreteHandlerA extends Handler{
public ConcreteHandlerA(String name) {
super(name);
}
@Override
public void processRequest(Request request) {
if(request.getParam1()<0){
//处理逻辑
}else{
//调用下一个handler的处理方法去处理
if (handler != null) {
handler.processRequest(request);
}else{
//默认处理
}
}
}
}
//具体处理者B
class ConcreteHandlerB extends Handler{
public ConcreteHandlerB(String name) {
super(name);
}
@Override
public void processRequest(Request request) {
if(request.getParam1()==0){
//处理逻辑
}else{
//调用下一个handler的处理方法去处理
if (handler != null) {
handler.processRequest(request);
}else{
//默认处理
}
}
}
}
//调用
class use{
public static void main(String[] args) {
Request request = new Request(1, 2);
ConcreteHandlerA handlerA = new ConcreteHandlerA("HandlerA");
ConcreteHandlerA handlerB = new ConcreteHandlerA("HandlerB");
//设置责任关系
handlerA.setHandler(handlerB);
//如果没人能处理则默认处理
handlerA.processRequest(request);
}
}
- 优缺点说明
- 将请求和处理分开,实现解耦,提高系统的灵活性
- 简化了对象,使对象不需要知道链的结构
- 性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在Handler中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
- 调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂
- 最佳应用场景: 有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪等审批流程、 Java Web中Tomcat对Encoding的处理、拦截器
22.2 源码分析
public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
//HandlerExecutionChain 主要负责的是请求拦截器的执行和请求处理,但是他本身不
//处理请求,只是将请求分配给链上注册处理器执行, 这是职责链实现方式,减少职责
//链本身与处理逻辑之间的耦合,规范了处理流程
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
//获取到HandlerExecutionChain对象
mappedHandler = this.getHandler(processedRequest);
//...
//调用了HandlerExecutionChain中的applyPreHandle方法
//在applyPreHandle内部得到了HandlerInterceptor拦截器,并调用了它的preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//...
//如果上面applyPreHandle()调用处理失败则返回,成功则接着执行applyPostHandle方法
//形成责任链
mappedHandler.applyPostHandle(processedRequest, response, mv);
public class HandlerExecutionChain {
private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);
private final Object handler;
@Nullable
//HandlerExecutionChain 维护了 HandlerInterceptor 的集合, 可以向其中注册相应的拦截器
private HandlerInterceptor[] interceptors;
@Nullable
private List<HandlerInterceptor> interceptorList;
private int interceptorIndex;
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) throws Exception {
HandlerInterceptor[] interceptors = this.getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for(int i = this.interceptorIndex; i >= 0; --i) {
HandlerInterceptor interceptor = interceptors[i];
try {
//此处调用了拦截器的afterCompletion方法
interceptor.afterCompletion(request, response, this.handler, ex);
} catch (Throwable var8) {
logger.error("HandlerInterceptor.afterCompletion threw exception", var8);
}
}
}
SpringMVC-HandlerExecutionChain 类就使用到职责链模式