Java后端架构师的成长之路(二)——Java设计模式(3)

Java设计模式

  • 23种设计模式
    • 行为型模式
      • 模板方法模式
        • 豆浆制作问题
        • 模板方法模式基本介绍
        • 模板方法模式原理类图
        • 模板方法模式解决豆浆制作问题
        • 模板方法模式的钩子方法
        • 模板方法模式在Spring框架应用的源码分析
        • 模板方法模式的注意事项和细节
      • 命令模式
        • 智能生活项目需求
        • 命令模式基本介绍
        • 命令模式的原理类图
        • 命令模式解决智能生活项目
        • 令模式在Spring框架JdbcTemplate应用的源码分析
        • 命令模式的注意事项和细节
      • 访问者模式
        • 测评系统的需求
        • 访问者模式基本介绍
        • 访问者模式的原理类图
        • 访问者模式应用实例
        • 访问者模式的注意事项和细节
      • 迭代器模式
        • 看一个具体的需求
        • 迭代器模式基本介绍
        • 迭代器模式的原理类图
        • 迭代器模式应用实例
        • 迭代器模式在JDK-ArrayList集合应用的源码分析
        • 迭代器模式的注意事项和细节
      • 观察者模式
        • 天气预报项目需求
        • 普通方案
        • 观察者模式(Observer)原理
        • 观察者模式解决天气预报需求
        • 观察者模式在Jdk应用的源码分析
      • 中介者模式
        • 智能家庭管理问题
        • 中介者模式基本介绍
        • 中介者模式的原理类图
        • 中介者模式应用实例
        • 中介者模式的注意事项和细节
      • 备忘录模式
        • 游戏角色状态恢复问题
        • 传统方案解决游戏角色恢复
        • 备忘录模式基本介绍
        • 备忘录模式原理类图
        • 备忘录模式原理代码实现
        • 备忘录模式的注意事项和细节
      • 解释器模式
        • 四则运算问题
        • 传统方案解决四则运算问题分析
        • 解释器模式基本介绍
        • 解释器模式的原理类图
        • 解释器模式来实现四则运算
        • 解释器模式在Spring框架应用的源码剖析
        • 解释器模式的注意事项和细节
      • 状态模式
        • APP抽奖活动问题
        • 状态模式基本介绍
        • 状态模式原理类图
        • 状态模式解决APP抽奖问题
        • 状态模式在实际项目-借贷平台源码分析
        • 状态模式的注意事项和细节
      • 策略模式
        • 鸭子问题
        • 策略模式基本介绍
        • 策略模式的原理类图
        • 策略模式解决鸭子问题
        • 策略模式在JDK-Arrays 应用的源码分析
        • 策略模式的注意事项和细节
      • 责任链模式
        • OA系统采购审批需求
        • 传统方案解决OA系统审批问题分析
        • 职责链模式基本介绍
        • 职责链模式的原理类图
        • 职责链模式解决OA系统采购审批
        • 职责链模式在SpringMVC的源码分析
        • 职责链模式的注意事项和细节
    • 设计模式在框架或项目中源码分析的说明

23种设计模式

行为型模式

模板方法模式

豆浆制作问题

  • 制作豆浆的流程:选材—>添加配料—>浸泡—>放到豆浆机打碎。
  • 通过添加不同的配料,可以制作出不同口味的豆浆。
  • 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的。
  • 请使用 模板方法模式 完成:因为模板方法模式,比较简单,很容易就想到这个方案,因此就直接使用,不再使用传统的方案来引出模板方法模式。

模板方法模式基本介绍

  • 模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
  • 简单说,模板方法模式 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤。
  • 这种类型的设计模式属于行为型模式。

模板方法模式原理类图

Java后端架构师的成长之路(二)——Java设计模式(3)_第1张图片

  • AbstractClass 抽象类, 类中实现了模板方法(template),定义了算法的骨架,具体子类需要去实现其它的抽象方法 operationr1,2,3。
  • ConcreteClass 实现抽象方法 operationr1,2,3, 以完成算法中特点子类的步骤。

模板方法模式解决豆浆制作问题

  • UML思路分析:
    Java后端架构师的成长之路(二)——Java设计模式(3)_第2张图片
  • 代码实现:
public class TemplateMothodCase {
    public static void main(String[] args) {
        System.out.println("----制作红豆豆浆----");
        RedBeanSoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
        redBeanSoyaMilk.make();

        System.out.println("----制作花生豆浆----");
        PeanutSoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
        peanutSoyaMilk.make();
    }
}
/**
 * 抽象类:豆浆
 */
abstract class SoyaMilk {
    /**
     * 模板方法make:模板方法可以做成 final, 不让子类去覆盖
     */
    final void make() {
        select();
        addCondiments();
        soak();
        beat();
    }
    /**
     * 选材料
     */
    void select() {
        System.out.println("第一步:选择好的新鲜黄豆");
    }
    /**
     * 添加不同的配料,抽象方法交给子类具体实现
     */
    abstract void addCondiments();
    /**
     * 浸泡
     */
    void soak() {
        System.out.println("第三步:黄豆和配料开始浸泡,需要3小时");
    }
    void beat() {
        System.out.println("第四步:黄豆和配料放到豆浆机打碎");
    }
}
/**
 * 具体的子类
 */
class RedBeanSoyaMilk extends SoyaMilk {
    @Override
    void addCondiments() {
        System.out.println("加入上好的红豆");
    }
}
class PeanutSoyaMilk extends SoyaMilk {
    @Override
    void addCondiments() {
        System.out.println("加入上好的花生");
    }
}

模板方法模式的钩子方法

  • 在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。
  • 还是用上面做豆浆的例子来讲解,比如,我们还希望制作纯豆浆,不添加任何的配料,请使用钩子方法对前面的模板方法进行改造。
abstract class SoyaMilk {
    /**
     * 模板方法make:模板方法可以做成 final, 不让子类去覆盖
     */
    final void make() {
        select();
        if (customerWantCondiments()) {
            addCondiments();
        }
        soak();
        beat();
    }
    // 省略其它...
    /**
     * 钩子方法,决定是否需要添加配料
     */
    boolean customerWantCondiments() {
        return true;
    }
}
/**
 * 纯豆浆
 */
class PureSoyaMilk extends SoyaMilk {
    @Override
    void addCondiments() {
        // 空实现
    }
    @Override
    boolean customerWantCondiments() {
        return false;
    }
}

模板方法模式在Spring框架应用的源码分析

  • Spring IOC容器初始化时运用到的模板方法模式:obtainFreshBeanFactory、onRefresh
/**
 * 完成IoC容器的创建及初始化工作
 */
@Override
public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		// Prepare this context for refreshing.
           // STEP 1: 刷新预处理
		prepareRefresh();

		// Tell the subclass to refresh the internal bean factory.
           // STEP 2:
           // 		a) 创建IoC容器(DefaultListableBeanFactory)
           //		b) 加载解析XML文件(最终存储到Document对象中)
           //		c) 读取Document对象,并完成BeanDefinition的加载和注册工作
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		// Prepare the bean factory for use in this context.
           // STEP 3: 对IoC容器进行一些预处理(设置一些公共属性)
		prepareBeanFactory(beanFactory);

		try {
			// Allows post-processing of the bean factory in context subclasses.
               // STEP 4: 
			postProcessBeanFactory(beanFactory);

			// Invoke factory processors registered as beans in the context.
			// STEP 5: 调用BeanFactoryPostProcessor后置处理器对BeanDefinition处理
               invokeBeanFactoryPostProcessors(beanFactory);

			// Register bean processors that intercept bean creation.
			// STEP 6: 注册BeanPostProcessor后置处理器
               registerBeanPostProcessors(beanFactory);

			// Initialize message source for this context.
			// STEP 7: 初始化一些消息源(比如处理国际化的i18n等消息源)
               initMessageSource();

			// Initialize event multicaster for this context.
			// STEP 8: 初始化应用事件广播器
               initApplicationEventMulticaster();

			// Initialize other special beans in specific context subclasses.
			// STEP 9: 初始化一些特殊的bean,是一个钩子方法
               onRefresh();

			// Check for listener beans and register them.
			// STEP 10: 注册一些监听器
               registerListeners();

			// Instantiate all remaining (non-lazy-init) singletons.
			// STEP 11: 实例化剩余的单例bean(非懒加载方式)
               // 注意事项:Bean的IoC、DI和AOP都是发生在此步骤
               finishBeanFactoryInitialization(beanFactory);

			// Last step: publish corresponding event.
			// STEP 12: 完成刷新时,需要发布对应的事件
               finishRefresh();
		}
		// 省略...
	}
}
	↓↓↓↓↓
// 也用到了模板方法模式
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
	// 主要是通过该方法完成IoC容器的刷新
	refreshBeanFactory();	
	ConfigurableListableBeanFactory beanFactory = getBeanFactory();
	if (logger.isDebugEnabled()) {
		logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
	}
	return beanFactory;
}
	↓↓↓↓↓
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
@Override
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

Java后端架构师的成长之路(二)——Java设计模式(3)_第3张图片

模板方法模式的注意事项和细节

  • 基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改。
  • 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。
  • 统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。
  • 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大。
  • 一般模板方法都加上final关键字, 防止子类重写模板方法。
  • 模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤 ,这一系列的步骤基本相同,但其个别步骤在实现时 可能不同,通常考虑用模板方法模式来处理

命令模式

智能生活项目需求

  • 我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装app就可以控制对这些家电工作。
  • 这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个App,分别控制,我们希望只要一个app就可以控制全部智能家电。
  • 要实现一个app控制所有智能家电的需要,则每个智能家电厂家都要提供一个统一的接口给app调用,这时 就可以考虑使用命令模式。
  • 命令模式可将“动作的请求者”从“动作的执行者”对象中解耦出来。
  • 在我们的例子中,动作的请求者是手机app,动作的执行者是每个厂商的一个家电产品。

命令模式基本介绍

  • 命令模式(Command Pattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计。
  • 命名模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
  • 在命名模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作。
  • 通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。
  • Invoker是调用者(将军),Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,持有接收对象。

命令模式的原理类图

Java后端架构师的成长之路(二)——Java设计模式(3)_第4张图片

  • Invoker:是调用者角色
  • Command:是命令角色,需要执行的所有命令都在这里,可以是接口或抽象类
  • Receiver:接受者角色,知道如何实施和执行一个请求相关的操作
  • ConcreteCommand:将一个接受者对象与一个动作绑定,调用接受者相应的操作,实现execute

命令模式解决智能生活项目

  • 思路分析:
    Java后端架构师的成长之路(二)——Java设计模式(3)_第5张图片
  • 代码实现:
public class CommandCase {
    public static void main(String[] args) {
        // 创建点灯对象(接收者)
        LightReceiver receiver = new LightReceiver();
        // 创建点灯相关的开关命令
        LightOnCommand lightOnCommand = new LightOnCommand(receiver);
        LightOffCommand lightOffCommand = new LightOffCommand(receiver);
        // 创建一个遥控器
        RemoteController remoteController = new RemoteController();
        // 给遥控器设置命令
        remoteController.setCommand(0, lightOnCommand, lightOffCommand);

        System.out.println("测试遥控器");
        System.out.println("--------按下灯的开按钮--------");
        remoteController.onButtonWasPushed(0);
        System.out.println("--------按下灯的关按钮--------");
        remoteController.offButtonWasPushed(0);
        System.out.println("--------按下撤销按钮--------");
        remoteController.undoButtonWasPushed();
    }
}
/**
 * 命令接口
 */
interface Command {
    void execute();
    void undo();
}
/**
 * 命令接收者
 */
class LightReceiver {
    public void on() {
        System.out.println("点灯打开了...");
    }
    public void off() {
        System.out.println("点灯关闭了...");
    }
}
/**
 * 具体命令
 */
class LightOnCommand implements Command {
    /**
     * 聚合点灯命令接收者
     */
    private LightReceiver receiver;
    LightOnCommand(LightReceiver receiver) {
        this.receiver = receiver;
    }
    @Override
    public void execute() {
        receiver.on();
    }
    @Override
    public void undo() {
        receiver.off();
    }
}
class LightOffCommand implements Command {
    /**
     * 聚合点灯命令接收者
     */
    private LightReceiver receiver;
    LightOffCommand(LightReceiver receiver) {
        this.receiver = receiver;
    }
    @Override
    public void execute() {
        receiver.off();
    }
    @Override
    public void undo() {
        receiver.on();
    }
}
/**
 * 空命令:用于初始化每个按钮,当调用空命令时,对象什么都不做
 * 其实这也是一种设计模式,可以省掉对空的判断
 */
class NoCommand implements Command {
    @Override
    public void execute() { }
    @Override
    public void undo() { }
}
/**
 * 命令的调用者/发布者:遥控器
 */
class RemoteController {
    private Command[] onCommands;
    private Command[] offCommands;
    /**
     * 撤销命令
     */
    private Command undoCommand;
    RemoteController() {
        onCommands = initCommand(5);
        offCommands = initCommand(5);
    }
    private Command[] initCommand(int num) {
        Command[] commands = new Command[num];
        for (int i = 0; i < num; i++) {
            commands[i] = new NoCommand();
        }
        return commands;
    }
    /**
     * 给遥控器按钮设置需要的命令
     */
    public void setCommand(int no, Command onCommand, Command offCommand) {
        onCommands[no] = onCommand;
        offCommands[no] = offCommand;
    }
    /**
     * 按下开按钮
     */
    public void onButtonWasPushed(int no) {
        // 找到你按下的 开 的按钮,并调用对应的方法
        onCommands[no].execute();
        // 记录这次操作,用于撤销
        undoCommand = onCommands[no];
    }
    /**
     * 按下关按钮
     */
    public void offButtonWasPushed(int no) {
        // 找到你按下的 关 的按钮,并调用对应的方法
        offCommands[no].execute();
        // 记录这次操作,用于撤销
        undoCommand = offCommands[no];
    }
    /**
     * 按下撤销按钮
     */
    public void undoButtonWasPushed() {
        undoCommand.undo();
    }
}
//测试遥控器
//--------按下灯的开按钮--------
//点灯打开了
//--------按下灯的关按钮--------
//点灯关闭了
//--------按下撤销按钮--------
//点灯打开了

令模式在Spring框架JdbcTemplate应用的源码分析

  • Spring框架的JdbcTemplate就使用到了命令模式
  • StatementCallback 接口,类似命令接口(Command)。
@FunctionalInterface
public interface StatementCallback<T> {
	@Nullable
	T doInStatement(Statement stmt) throws SQLException, DataAccessException;
}
  • class QueryStatementCallback implements StatementCallback, SqlProvider:匿名内部类, 实现了命令接口, 同时也充当命令接收者。
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
	@Override
	public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
		return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));
	}
	↓↓↓↓↓
	@Override
	@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 (logger.isDebugEnabled()) {
			logger.debug("Executing SQL query [" + sql + "]");
		}

		class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
			@Override
			@Nullable
			// !!!!!!!核心
			public T doInStatement(Statement stmt) throws SQLException {
				ResultSet rs = null;
				try {
					rs = stmt.executeQuery(sql);
					return rse.extractData(rs);
				}
				finally {
					JdbcUtils.closeResultSet(rs);
				}
			}
			@Override
			public String getSql() {
				return sql;
			}
		}
		// !!!!!!!核心
		return execute(new QueryStatementCallback());
	}
	↓↓↓↓↓
	@Override
	@Nullable
	public <T> T execute(StatementCallback<T> action) throws DataAccessException {
		Assert.notNull(action, "Callback object must not be null");

		Connection con = DataSourceUtils.getConnection(obtainDataSource());
		Statement stmt = null;
		try {
			stmt = con.createStatement();
			applyStatementSettings(stmt);
			// !!!!核心
			T result = action.doInStatement(stmt);
			handleWarnings(stmt);
			return result;
		}
		catch (SQLException ex) {
			// Release Connection early, to avoid potential connection pool deadlock
			// in the case when the exception translator hasn't been initialized yet.
			String sql = getSql(action);
			JdbcUtils.closeStatement(stmt);
			stmt = null;
			DataSourceUtils.releaseConnection(con, getDataSource());
			con = null;
			throw translateException("StatementCallback", sql, ex);
		}
		finally {
			JdbcUtils.closeStatement(stmt);
			DataSourceUtils.releaseConnection(con, getDataSource());
		}
	}
}
  • 命令调用者是 JdbcTemplate,其中execute(StatementCallback action) 方法中,调用action.doInStatement 方法. 不同的 实现 StatementCallback 接口的对象,对应不同的doInStatemnt 实现逻辑。
  • 另外实现 StatementCallback 命令接口的子类还有 QueryStatementCallback等。
    Java后端架构师的成长之路(二)——Java设计模式(3)_第6张图片

命令模式的注意事项和细节

  • 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说【请求发起者】和【请求执行者】之间的解耦是通过命令对象实现的,命令对象起到 了纽带桥梁的作用。
  • 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令。
  • 容易实现对请求的撤销和重做。
  • 命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在在使用的时候要注意。
  • 空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。
  • 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟CMD(DOS命令)订单的撤销/恢复、触发-反馈机制

访问者模式

测评系统的需求

  • 将观众分为男人和女人,对歌手进行测评,当看完某个歌手表演后,得到他们对该歌手不同的评价(评价有不同的种类,比如 成功、失败 等)
  • 传统方案:继承
  • 如果系统比较小,还是ok的,但是考虑系统增加越来越多新的功能时,对代码改动较大,违反了ocp原则, 不利于维护。
  • 扩展性不好,比如 增加了 新的人员类型,或者管理方法,都不好做。
  • 引出我们会使用新的设计模式 – 访问者模式

访问者模式基本介绍

  • 访问者模式(Visitor Pattern),封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
  • 主要将数据结构与数据操作分离,解决 数据结构和操作耦合性 问题。
  • 访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者的接口
  • 访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),同时需要避免让这些操作"污染"这些对象的类,可以选用访问者模式解决。

访问者模式的原理类图

Java后端架构师的成长之路(二)——Java设计模式(3)_第7张图片

  • Visitor:是抽象访问者,为该对象结构中的 ConcreteElement 的每一个类声明一个visit操作。
  • ConcreteVisitor:是一个具体的访问值,实现每个由 Visitor 声明的操作,是每个操作实现的部分。
  • ObjectStructure:能枚举它的元素, 可以提供一个高层的接口,用来允许访问者访问元素。
  • Element:定义一个accept 方法,接收一个访问者对象。
  • ConcreteElement:为具体元素,实现了accept 方法。

访问者模式应用实例

  • 思路分析:
    Java后端架构师的成长之路(二)——Java设计模式(3)_第8张图片
  • 代码实现:
public class VisitorCase {
    public static void main(String[] args) {
        // 创建数据结构
        ObjectStructure structure = new ObjectStructure();
        structure.attach(new Male("Tom"));
        structure.attach(new Male("Jack"));
        structure.attach(new Male("Smith"));
        structure.attach(new Female("Lily"));
        structure.attach(new Female("Sari"));

        // 成功
        Succcess succcess = new Succcess();
        structure.display(succcess);
        // 失败
        Fail fail = new Fail();
        structure.display(fail);
    }
}
/**
 * 抽象访问者:测评的动作
 */
abstract class Action {
    abstract void getMaleResult(Male male);
    abstract void getFemaleResult(Female female);
}
/**
 * 具体访问者
 */
class Succcess extends Action {
    @Override
    void getMaleResult(Male male) {
        System.out.println(male.name + ": Yes");
    }
    @Override
    void getFemaleResult(Female female) {
        System.out.println(female.name + ": Yes");
    }
}
class Fail extends Action {
    @Override
    void getMaleResult(Male male) {
        System.out.println(male.name + ": No");
    }
    @Override
    void getFemaleResult(Female female) {
        System.out.println(female.name + ": No");
    }
}
/**
 * 抽象元素:人
 */
abstract class Person {
    public String name;
    Person(String name) {
        this.name = name;
    }
    abstract void accept(Action action);
}
/**
 * 具体的元素:男人和女人
 * 合理我们使用到了双分派,即首先在客户端程序中,将具体状态作为参数传递到了【Male、Female】中
 * 然后【Male、Female】类调用作为参数的"具体方法"中getXxxResult,同时将自己(this)作为参数传入,即第二次分派
 */
class Male extends Person {
    Male(String name) {
        super("[M]" + name);
    }
    @Override
    void accept(Action action) {
        action.getMaleResult(this);
    }
}
class Female extends Person {
    Female(String name) {
        super("[F]" + name);
    }
    @Override
    void accept(Action action) {
        action.getFemaleResult(this);
    }
}
/**
 * 数据结构:管理了很多人(Male、Female)
 */
class ObjectStructure {
    /**
     * 维护了一个集合
     */
    private List<Person> persons = new LinkedList<>();
    /**
     * 增加
     */
    public void attach(Person p) {
        persons.add(p);
    }
    /**
     * 移除
     */
    public void detach(Person p) {
        persons.remove(p);
    }
    /**
     * 显示
     */
    public void display(Action action) {
        for (Person p : persons) {
            p.accept(action);
        }
    }
}
  • 上面提到了双分派,所谓双分派是指不管类怎么变化,我们都能找到期望的方法运行。双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型。
  • 以上述实例为例,假设我们要添加一个Wait的状态类,考察Man类和Woman类的反应,由于使用了双分派,只需增加一个Action子类即可在客户端调用即可,不需要改动任何其他类的代码

访问者模式的注意事项和细节

  • 访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高。
  • 访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据结构相对稳定的系统。
  • 具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的,这样造成了具体元素变更比较困难。
  • 违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素。
  • 因此,如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的。

迭代器模式

看一个具体的需求

  • 编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。
  • 传统设计方案:将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的。
  • 实际上我们的要求是:在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系, 因此这种方案,不能很好实现的遍历的操作
  • 解决方案:迭代器模式
    Java后端架构师的成长之路(二)——Java设计模式(3)_第9张图片

迭代器模式基本介绍

  • 迭代器模式(Iterator Pattern)是常用的设计模式,属于行为型模式。
  • 如果我们的集合元素是用不同的方式实现的,有数组,还有java的集合类,或者还有其他方式,当客户端要遍历这些集合元素的时候就要使用多种遍历方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。
  • 迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素,不需要知道集合对象的底层表示,即:不暴露其内部的结构。

迭代器模式的原理类图

Java后端架构师的成长之路(二)——Java设计模式(3)_第10张图片

  • Iterator:迭代器接口,是系统提供,含义 hasNext、next、remove。
  • ConcreteIterator:具体的迭代器类,管理迭代。
  • Aggregate:一个统一的聚合接口,将客户端和具体聚合解耦。
  • ConcreteAggregate:具体的聚合,持有对象集合,并提供一个方法,返回一个迭代器,该迭代器可以正确遍历集合。
  • Client:客户端,通过 Iterator 和 Aggregate 依赖子类。

迭代器模式应用实例

  • 思路分析:
    Java后端架构师的成长之路(二)——Java设计模式(3)_第11张图片
  • 代码实现:
public class IteratorCase {
    public static void main(String[] args) {
        ComputerCollege computerCollege = new ComputerCollege();
        computerCollege.addDepartment("Java专业", "学习Java");
        computerCollege.addDepartment("Php专业", "学习Php");
        computerCollege.addDepartment("大数据专业", "学习大数据");

        InfoCollege infoCollege = new InfoCollege();
        infoCollege.addDepartment("信息安全专业", "学习信息安全");
        infoCollege.addDepartment("网络安全专业", "学习网络安全");

        List<College> colleges = new ArrayList<>();
        colleges.add(computerCollege);
        colleges.add(infoCollege);

        OutputImpl output = new OutputImpl(colleges);
        output.printCollege();
    }
}
/**
 * 系
 */
@Data
@AllArgsConstructor
class Department {
    private String name;
    private String desc;
}
/**
 * 计算机学院迭代器
 */
class ComputerCollegeIterator implements Iterator<Department> {
    /**
     * 这里我们定义 Department 是以数组形式存放
     */
    private Department[] departments;
    /**
     * 遍历的位置
     */
    private int position;
    ComputerCollegeIterator(Department[] departments) {
        this.departments = departments;
    }
    @Override
    public boolean hasNext() {
        return position < departments.length && departments[position] != null;
    }
    @Override
    public Department next() {
        Department department = departments[position];
        position += 1;
        return department;
    }
    @Override
    public void remove() {
    }
}
/**
 * 信息工程学院迭代器
 */
class InfoCollegeIterator implements Iterator<Department> {
    /**
     * 这里我们定义 Department 是以List集合形式存放
     */
    private List<Department> departments;
    /**
     * 遍历的索引位置
     */
    private int index = -1;
    InfoCollegeIterator(List<Department> departments) {
        this.departments = departments;
    }
    @Override
    public boolean hasNext() {
        if (index >= departments.size() - 1) {
            return false;
        } else {
            index += 1;
            return true;
        }
    }
    @Override
    public Department next() {
        return departments.get(index);
    }
    @Override
    public void remove() {
    }
}
/**
 * 聚合接口:学院
 */
interface College {
    String getName();
    void addDepartment(String name, String desc);
    Iterator<Department> createIterator();
}
/**
 * 计算机学院
 */
class ComputerCollege implements College {
    private Department[] departments;
    /**
     * 保存当前数组对象的个数
     */
    private int numOfDep;
    ComputerCollege() {
        departments = new Department[5];
    }
    @Override
    public String getName() {
        return "计算机学院";
    }
    @Override
    public void addDepartment(String name, String desc) {
        departments[numOfDep++] = new Department(name, desc);
    }
    @Override
    public Iterator<Department> createIterator() {
        return new ComputerCollegeIterator(departments);
    }
}
/**
 * 信息工程学院
 */
class InfoCollege implements College {
    private List<Department> departments;
    InfoCollege() {
        departments = new ArrayList<>();
    }
    @Override
    public String getName() {
        return "信息工程学院";
    }
    @Override
    public void addDepartment(String name, String desc) {
        departments.add(new Department(name, desc));
    }
    @Override
    public Iterator<Department> createIterator() {
        return new InfoCollegeIterator(departments);
    }
}
/**
 * 输出
 */
class OutputImpl {
    /**
     * 学院集合
     */
    private List<College> colleges;
    OutputImpl(List<College> colleges) {
        this.colleges = colleges;
    }
    /**
     * 遍历所有学院,调用 printDepartment
     */
    public void printCollege() {
        Iterator<College> iterator = colleges.iterator();
        while (iterator.hasNext()) {
            College college = iterator.next();
            System.out.println("----" + college.getName() + "----");
            printDepartment(college.createIterator());
        }
    }
    private void printDepartment(Iterator<Department> iterator) {
        while (iterator.hasNext()) {
            Department department = iterator.next();
            System.out.println(department.getName());
        }
    }
}

迭代器模式在JDK-ArrayList集合应用的源码分析

  • JDK的ArrayList 集合中就使用了迭代器模式
public class JDKIterator {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("jack");
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

Java后端架构师的成长之路(二)——Java设计模式(3)_第12张图片
Java后端架构师的成长之路(二)——Java设计模式(3)_第13张图片

  • 内部类 Itr 充当具体实现迭代器 Iterator 的类, 作为 ArrayList 内部类。
  • List 就是充当了聚合接口,含有一个 iterator() 方法,返回一个迭代器对象。
  • ArrayList 是实现聚合接口 List 的子类,实现了 iterator()。
  • Iterator 接口系统提供。
  • 迭代器模式解决了不同集合(ArrayList, LinkedList) 统一遍历问题

迭代器模式的注意事项和细节

  • 提供一个统一的方法遍历对象,客户不用再考虑聚合的类型,使用一种方法就可以遍历对象了。
  • 隐藏了聚合的内部结构,客户端要遍历聚合的时候只能取到迭代器,而不会知道聚合的具体组成。提供了一种设计思想,就是一个类应该只有一个引起变化的原因(叫做单一责任原则)。在聚合类中,我们把迭代器分开,就是要把管理对象集合遍历对象集合的责任分开,这样一来集合改变的话,只影响到聚合对象。而如果遍历方式改变的话,只影响到了迭代器。
  • 当要展示一组相似对象,或者遍历一组相同对象时使用,适合使用迭代器模式。
  • 缺点:每个聚合对象都要一个迭代器,会生成多个迭代器不好管理类。

观察者模式

天气预报项目需求

  • 气象站可以将每天测量到的温度、湿度、气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)。
  • 需要设计开放型API,便于其他第三方也能接入气象站获取数据。
  • 提供温度、气压和湿度的接口。
  • 测量数据更新时,要能实时的通知给第三方。

普通方案

  • 通过对气象站项目的分析,我们可以初步设计出一个WeatherData类。
    Java后端架构师的成长之路(二)——Java设计模式(3)_第14张图片
  • 通过getXxx方法,可以让第三方接入,并得到相关信息。
  • 当数据有更新时,气象站通过调用dataChange() 去更新数据,当第三方再次获取时,就能得到最新数据,当然也可以推送。
    Java后端架构师的成长之路(二)——Java设计模式(3)_第15张图片
  • 代码实现:
public class ObserverCase {
    public static void main(String[] args) {
        // 创建接入方 currentConditions
        CurrentConditions conditions = new CurrentConditions();
        // 创建 WeatherData 并将 接入方 currentConditions 传递到 WeatherData 中
        WeatherData weatherData = new WeatherData(conditions);
        // 更新天气情况
        weatherData.setData(30, 150, 40);

        //天气情况变化 
        System.out.println("============天气情况变化=============");
        weatherData.setData(40, 160, 20);
    }
}
/**
 * 显示当前天气情况(可以理解成是气象站自己的网站)
 */
class CurrentConditions {
    /**
     * 温度、气压、湿度
     */
    private float temperature;
    private float pressure;
    private float humidity;
    /**
     * 更新 天气情况,是由 WeatherData 来调用 ==>> 使用推送模式
     */
    public void update(float temperature, float pressure, float humidity) {
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;

        display();
    }
    /**
     * 显示
     */
    private void display() {
        System.out.println("***Today temperature: " + temperature + "***");
        System.out.println("***Today pressure: " + pressure + "***");
        System.out.println("***Today humidity: " + humidity + "***");
    }
}
/**
 * 类是核心
 * 1. 包含最新的天气情况信息
 * 2. 含有 CurrentConditions 对象
 * 3. 当数据有更新时,就主动的调用 CurrentConditions 对象 update 方法(含 display), 这样他们(接入方)就看到最新的信息
 */
class WeatherData {
    private float temperatrue;
    private float pressure;
    private float humidity;
    /**
     * 加入新的第三方
     */
    private CurrentConditions currentConditions;
    public WeatherData(CurrentConditions currentConditions) {
        this.currentConditions = currentConditions;
    }
    /**
     * 当数据有更新时,就调用 setData
     */
    public void setData(float temperature, float pressure, float humidity) {
        this.temperatrue = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        //调用 dataChange, 将最新的信息 推送给 接入方 currentConditions
        dataChange();
    }
    private void dataChange() {
        //调用接入方的 update
        currentConditions.update(getTemperature(), getPressure(), getHumidity());
    }
    private float getTemperature() {
        return temperatrue;
    }
    private float getPressure() {
        return pressure;
    }
    private float getHumidity() {
        return humidity;
    }
}
  • 问题分析:其他第三方接入气象站获取数据的问题时,无法在运行时动态的添加第三方 (新浪网站),违反ocp原则=>*观察者模式
  • //在WeatherData中,当增加一个第三方,都需要创建一个对应的第三方的公告板对象,并加入到dataChange, 不利于维护,也不是动态加入
public void dataChange() {
	currentConditions.update(getTemperature(), getPressure(), getHumidity());
}

观察者模式(Observer)原理

  • 观察者模式类似订牛奶业务:奶站/气象局Subject,用户/第三方网站Observer。
  • Subject:登记注册、移除和通知
    Java后端架构师的成长之路(二)——Java设计模式(3)_第16张图片
* registerObserver 注册
* removeObserver 移除
* notifyObservers() 通知所有的注册的用户,根据不同需求,可以是更新数据,让用户来取,也可能是实施推送,看具体需求定
  • Observer:接收输入
    Java后端架构师的成长之路(二)——Java设计模式(3)_第17张图片
  • 观察者模式:对象之间多对一依赖的一种设计方案,被依赖的对象为Subject,依赖的对象为Observer,Subject通知Observer变化,比如这里的牛奶站是Subject,是一的一方。用户时Observer,是多的一方。
  • 原理UML类图:
    Java后端架构师的成长之路(二)——Java设计模式(3)_第18张图片

观察者模式解决天气预报需求

  • 思路分析:
    Java后端架构师的成长之路(二)——Java设计模式(3)_第19张图片
  • 代码实现:
public class ObserverCase {
    public static void main(String[] args) {
        // 创建一个 WeatherData
        WeatherData weatherData = new WeatherData();
        // 创建观察者
        CurrentCondition currentConditions = new CurrentCondition();
        BaiduSite baiduSite = new BaiduSite();

        // 注册到 weatherData
        weatherData.registerObserver(currentConditions);
        weatherData.registerObserver(baiduSite);

        // 测试
        System.out.println("通知各个注册的观察者, 看看信息");
        weatherData.setData(10f, 100f, 30.3f);

        // 测试移除
        weatherData.removeObserver(currentConditions);
        System.out.println(); System.out.println("通知各个注册的观察者, 看看信息");
        weatherData.setData(10f, 100f, 30.3f);
    }
}
/**
 * 目标接口
 */
interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}
/**
 * 观察者接口
 */
interface Observer {
    void update(float temperature, float pressure, float humidity);
}
/**
 * 具体目标-核心:天气信息
 * 1. 包含最新的天气情况信息
 * 2. 含有 观察者集合,使用 ArrayList 管理
 * 3. 当数据有更新时,就主动的调用 ArrayList, 通知所有的(接入方)就看到最新的信息
 */
class WeatherData implements Subject {
    private float temperatrue;
    private float pressure;
    private float humidity;
    /**
     * 观察者集合
     */
    private List<Observer> observers = new ArrayList<>();

    public void setData(float temperature, float pressure, float humidity) {
        this.temperatrue = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        //调用 dataChange, 将最新的信息 推送给 接入方 currentConditions
        dataChange();
    }
    private void dataChange() {
        notifyObservers();
    }
    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }
    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }
    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(temperatrue, pressure, humidity);
        }
    }
}
/**
 * 具体观察者
 */
class CurrentCondition implements Observer {
    /**
     * 温度、气压、湿度
     */
    private float temperature;
    private float pressure;
    private float humidity;
    /**
     * 更新 天气情况,是由 WeatherData 来调用 ==>> 使用推送模式
     */
    @Override
    public void update(float temperature, float pressure, float humidity) {
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;

        display();
    }
    /**
     * 显示
     */
    private void display() {
        System.out.println("====气象局====");
        System.out.println("***Today temperature: " + temperature + "***");
        System.out.println("***Today pressure: " + pressure + "***");
        System.out.println("***Today humidity: " + humidity + "***");
    }
}
class BaiduSite implements Observer {
    private float temperature;
    private float pressure;
    private float humidity;
    @Override
    public void update(float temperature, float pressure, float humidity) {
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        display();
    }
    private void display() {
        System.out.println("====百度网站====");
        System.out.println("***百度网站 气温 : " + temperature + "***");
        System.out.println("***百度网站 气压: " + pressure + "***");
        System.out.println("***百度网站 湿度: " + humidity + "***");
    }
}

观察者模式在Jdk应用的源码分析

  • Jdk的 Observable 类就使用了观察者模式
public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;
    public Observable() {
        obs = new Vector<>();
    }
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }
    public void notifyObservers() {
        notifyObservers(null);
    }
    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();
    }
}
	↓↓↓↓↓
public interface Observer {
    void update(Observable o, Object arg);
}
  • Observable 的作用和地位等价于 我们前面讲过Subject。
  • Observable 是类,不是接口,类中已经实现了核心的方法 ,即管理Observer的方法 add… delete … notify…。
  • Observer 的作用和地位等价于我们前面讲过的 Observer,有update方法。
  • Observable 和 Observer 的使用方法和前面讲过的一样,只是Observable 是类,通过继承来实现观察者模式

中介者模式

智能家庭管理问题

  • 智能家庭包括各种设备,闹钟、咖啡机、电视机、窗帘等。
  • 主人要看电视时,各个设备可以协同工作,自动完成看电视的准备工作,比如流程为:铃响起 => 咖啡机开始做咖啡 => 窗帘自动落下 => 电视机开始播放。

中介者模式基本介绍

  • 中介者模式(MediatorPattern),用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
  • 中介者模式属于行为型模式,使代码易于维护。
  • 比如MVC模式,C(Controller控制器)是M(Model模型)和V(View视图)的中介者,在前后端交互时起到了中间人的作用。

中介者模式的原理类图

Java后端架构师的成长之路(二)——Java设计模式(3)_第20张图片

  • Mediator:就是抽象中介者,定义了同事对象到中介者对象的接口。
  • Colleague:是抽象同事类。
  • ConcreteMediator:具体的中介者对象, 实现抽象方法, 他需要知道所有的具体的同事类,即以一个集合来管理HashMap,并接受某个同事对象消息,完成相应的任务。
  • ConcreteColleague:具体的同事类,会有很多, 每个同事只知道自己的行为, 而不了解其他同事类的行为(方法),但是他们都依赖中介者对象。

中介者模式应用实例

  • 思路分析:
    Java后端架构师的成长之路(二)——Java设计模式(3)_第21张图片
  • 代码实现:
public class MediatorCase {
    public static void main(String[] args) {
        // 创建一个中介者对象
        Mediator mediator = new ConcreteMediator();
        // 创建一个 Alarm 并加入到 ConcreteMediator 的Map中
        Alarm alarm = new Alarm(mediator, "alarm");
        // 创建一个 CoffeeMachine 并加入到 ConcreteMediator 的Map中
        CoffeeMachine coffeeMachine = new CoffeeMachine(mediator, "coffeeMachine");
        // 创建一个 Curtains 并加入到 ConcreteMediator 的Map中
        Curtains curtains = new Curtains(mediator, "curtains");
        TV tv = new TV(mediator, "tv");
        // 让闹钟发出消息
        alarm.sendMessage(0);
        coffeeMachine.finishCoffee();
        alarm.sendMessage(1);
    }
}
/**
 * 中介者:抽象类
 */
abstract class Mediator {
    /**
     * 将一个同事类加入到集合
     */
    abstract void register(String colleagueName, Colleague colleague);
    /**
     * 接收消息,具体的同事对象发出
     */
    abstract void getMessage(int stateChange, String colleagueName);
    abstract void sendMessage();
}
/**
 * 具体的中介者类
 */
class ConcreteMediator extends Mediator {
    /**
     * 集合:放入了所有的同事类
     */
    private Map<String, Colleague> colleagueMap;
    private Map<String, String> interMap;
    ConcreteMediator() {
        colleagueMap = new HashMap<>();
        interMap = new HashMap<>();
    }
    @Override
    void register(String colleagueName, Colleague colleague) {
        colleagueMap.put(colleagueName, colleague);
        if (colleague instanceof Alarm) {
            interMap.put("Alarm", colleagueName);
        } else if (colleague instanceof CoffeeMachine) {
            interMap.put("CoffeeMachine", colleagueName);
        } else if (colleague instanceof TV) {
            interMap.put("TV", colleagueName);
        } else if (colleague instanceof Curtains) {
            interMap.put("Curtains", colleagueName);
        }
    }
    @Override
    void getMessage(int stateChange, String colleagueName) {
        Colleague colleague = colleagueMap.get(colleagueName);
        if (colleague instanceof Alarm) {
            if (stateChange == 0) {
                ((CoffeeMachine) colleagueMap.get(interMap.get("CoffeeMachine"))).startCoffee();
                ((TV) colleagueMap.get(interMap.get("TV"))).startTV();
            } else if (stateChange == 1) {
                ((TV) colleagueMap.get(interMap.get("TV"))).stopTV();
            }
        } else if (colleague instanceof CoffeeMachine) {
            ((Curtains) colleagueMap.get(interMap.get("Curtains"))).upCurtains();
        } else if (colleague instanceof TV) {
            // ...
        } else if (colleague instanceof Curtains) {
            // ...
        }
    }
    @Override
    void sendMessage() {

    }
}
/**
 * 同事:抽象类
 */
abstract class Colleague {
    private Mediator mediator;
    protected String name;
    Colleague(Mediator mediator, String name) {
        this.mediator = mediator;
        this.name = name;
    }
    public Mediator getMediator() {
        return mediator;
    }
    abstract void sendMessage(int stateChange);
}
/**
 * 具体的同事类
 */
class Alarm extends Colleague {
    Alarm(Mediator mediator, String name) {
        super(mediator, name);
        // 在创建Alarm对象时,将自己放入到ConcreteMediator对象中
        mediator.register(name, this);
    }
    @Override
    void sendMessage(int stateChange) {
        // 调用中介者对象的 getMessage
        this.getMediator().getMessage(stateChange, this.name);
    }
    public void sendAlarm(int stateChange) {
        this.sendMessage(stateChange);
    }
}
class CoffeeMachine extends Colleague {
    CoffeeMachine(Mediator mediator, String name) {
        super(mediator, name);
        mediator.register(name, this);
    }
    @Override
    void sendMessage(int stateChange) {
        this.getMediator().getMessage(stateChange, this.name);
    }
    public void startCoffee() {
        System.out.println("It's time to start coffee!");
    }
    public void finishCoffee() {
        System.out.println("After 5 minutes!");
        System.out.println("Coffee is OK!");
        sendMessage(0);
    }
}
class TV extends Colleague {
    TV(Mediator mediator, String name) {
        super(mediator, name);
        mediator.register(name, this);
    }
    @Override
    void sendMessage(int stateChange) {
        this.getMediator().getMessage(stateChange, this.name);
    }
    public void startTV() {
        System.out.println("It's time to Start TV!");
    }
    public void stopTV() {
        System.out.println("Stop TV!");
    }
}
class Curtains extends Colleague {
    Curtains(Mediator mediator, String name) {
        super(mediator, name);
        mediator.register(name, this);
    }
    @Override
    void sendMessage(int stateChange) {
        this.getMediator().getMessage(stateChange, this.name);
    }
    public void upCurtains() {
        System.out.println("I am holding Up Curtains!");
    }
}
//It's time to start coffee!
//It's time to Start TV!
//After 5 minutes!
//Coffee is OK!
//I am holding Up Curtains!
//Stop TV!

中介者模式的注意事项和细节

  • 多个类相互耦合,会形成网状结构, 使用中介者模式将网状结构分离为星型结构,进行解耦。
  • 减少类间依赖,降低了耦合,符合迪米特原则。
  • 中介者承担了较多的责任,一旦中介者出现了问题,整个系统就会受到影响
  • 如果设计不当,中介者对象本身变得过于复杂,这点在实际使用时,要特别注意。

备忘录模式

游戏角色状态恢复问题

  • 游戏角色有攻击力和防御力,在大战Boss前保存自身的状态(攻击力和防御力),当大战Boss后攻击力和防御力下降,从备忘录对象恢复到大战前的状态。

传统方案解决游戏角色恢复

Java后端架构师的成长之路(二)——Java设计模式(3)_第22张图片

  • 一个对象,就对应一个保存对象状态的对象, 这样当我们游戏的对象很多时,不利于管理,开销也很大。
  • 传统的方式是简单地做备份,new出另外一个对象出来,再把需要备份的数据放到这个新对象,但这就暴露了对象内部的细节。
  • 解决方案:=>备忘录模式。

备忘录模式基本介绍

  • 备忘录模式(Memento Pattern)在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
  • 可以这里理解备忘录模式:现实生活中的备忘录是用来记录某些要去做的事情,或者是记录已经达成的共同意见的事情,以防忘记了。而在软件层面,备忘录模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作。

备忘录模式原理类图

Java后端架构师的成长之路(二)——Java设计模式(3)_第23张图片

  • Originator:对象(需要保存状态的对象)
  • Memento:备忘录对象,负责保存好记录,即Originator内部状态
  • Caretaker:守护者对象,负责保存多个备忘录对象,使用集合管理,提高效率
  • 如果希望保存多个originator对象的不同时间的状态,也可以,只需要要 HashMap

备忘录模式原理代码实现

public class MementoCase {
    public static void main(String[] args) {
        Originator originator = new Originator();
        Caretaker caretaker = new Caretaker();
        originator.setState("状态1 攻击力100");
        // 保存当前状态
        caretaker.add(originator.saveStateMemento());
        System.out.println("当前的状态是:" + originator.getState());

        originator.setState("状态2 攻击力80");
        caretaker.add(originator.saveStateMemento());
        System.out.println("当前的状态是:" + originator.getState());
        
        originator.setState("状态3 攻击力30");
        caretaker.add(originator.saveStateMemento());

        System.out.println("当前的状态是:" + originator.getState());
        // 状态恢复
        originator.recoveryState(caretaker.get(1));
        System.out.println("恢复到状态2:" + originator.getState());
    }
}
@Data
class Originator {
    private String state;
    /**
     * 编写一个方法,可以保存一个状态对象Memento
     */
    public Memento saveStateMemento() {
        return new Memento(state);
    }
    /**
     * 通过备忘录对象,恢复状态
     */
    public void recoveryState(Memento memento) {
        state = memento.getState();
    }
}
class Memento {
    private String state;
    Memento(String state) {
        this.state = state;
    }
    public String getState() {
        return state;
    }
}
class Caretaker {
    /**
     * 在List集合中有很多备忘录对象
     */
    private List<Memento> mementos = new ArrayList<>();
    public void add(Memento memento) {
        mementos.add(memento);
    }
    public Memento get(int index) {
        return mementos.get(index);
    }
}

备忘录模式的注意事项和细节

  • 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
  • 实现了信息的封装,使得用户不需要关心状态的保存细节。
  • 如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存, 这个需要注意。
  • 适用的应用场景:① 后悔药、② 打游戏时的存档、③ Windows 里的 ctri + z、④ IE 中的后退、⑤ 数据库的事务管理。
  • 为了节约内存,备忘录模式可以和原型模式配合使用。

解释器模式

四则运算问题

通过解释器模式来实现四则运算,如计算a+b-c的值,具体要求:

  • 先输入表达式的形式,比如 a+b+c-d+e, 要求表达式的字母不能重复
  • 再分别输入a ,b, c, d, e 的值
  • 最后求出结果,如图:
    Java后端架构师的成长之路(二)——Java设计模式(3)_第24张图片

传统方案解决四则运算问题分析

  • 编写一个方法,接收表达式的形式,然后根据用户输入的数值进行解析,得到结果
  • 问题分析:如果加入新的运算符,比如 * / ( 等等,不利于扩展,另外让一个方法来解析会造成程序结构混乱,不够清晰。
  • 解决方案:可以考虑使用解释器模式, 即: 表达式 -> 解释器(可以有多种) -> 结果

解释器模式基本介绍

  • 在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这的词法分析器和语法分析器都可以看做是解释器
  • 解释器模式(Interpreter Pattern):是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)。
  • 应用场景:应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树、一些重复出现的问题可以用一种简单的语言来表达、一个简单语法需要解释的场景。
  • 这样的例子还有,比如编译器、运算表达式计算、正则表达式、机器人等。

解释器模式的原理类图

Java后端架构师的成长之路(二)——Java设计模式(3)_第25张图片

  • Context:是环境角色,含有解释器之外的全局信息
  • AbstractExpression:抽象表达式, 声明一个抽象的解释操作,这个方法为抽象语法树中所有的节点所共享
  • TerminalExpression:为终结符表达式, 实现与文法中的终结符相关的解释操作
  • NonTermialExpression:为非终结符表达式,为文法中的非终结符实现解释操作
  • 说明:输入Context 和 TerminalExpression 信息通过 Client 输入即可

解释器模式来实现四则运算

  • 思路分析:
    Java后端架构师的成长之路(二)——Java设计模式(3)_第26张图片
  • 代码实现:
public class InterpreterCase {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String expStr = getExpStr(scanner);
        Map<String, Integer> var = getValue(expStr, scanner);
        Calculator calculator = new Calculator(expStr);
        System.out.println(expStr + "的运算结果:" + calculator.run(var));
    }

    private static String getExpStr(Scanner scanner) {
        System.out.println("请输入表达式:");
        return scanner.nextLine();
    }
    private static Map<String, Integer> getValue(String expStr, Scanner scanner) {
        Map<String, Integer> map = new HashMap<>(8);

        for (char ch : expStr.toCharArray()) {
            if (ch != '+' && ch != '-') {
                String key = String.valueOf(ch);
                if (!map.containsKey(key)) {
                    System.out.println("请输入" + key +"的值");
                    int value = scanner.nextInt();
                    map.put(key, value);
                }
            }
        }

        return map;
    }
}
/**
 * 抽象类表达式,通过Map键值对,可以获取到变量的值
 */
abstract class AbsExpression {
    /**
     * a + b + c
     * 解释公式和值,key就是公式(表达式)参数[a, b, c],value就是具体值
     * 比如: {a: 10, b: 20}
     */
    abstract int interpret(Map<String, Integer> var);
}
/**
 * 变量的解释器
 */
class VarExpression extends AbsExpression {
    /**
     * 变量的key,key=a,b,c,...
     */
    private String key;
    VarExpression(String key) {
        this.key = key;
    }
    @Override
    int interpret(Map<String, Integer> var) {
        return var.get(this.key);
    }
}
/**
 * 抽象运算符号解释器,这里每个运算符号都只和自己左右两个数字有关系
 * 但左右两个数字有可能也是一个解析的结果,无论何种类型,都是Expression的实现类
 */
class SymbolExpression extends AbsExpression {
    protected AbsExpression left;
    protected AbsExpression right;
    SymbolExpression(AbsExpression left, AbsExpression right) {
        this.left = left;
        this.right = right;
    }
    /**
     * 这里是一个空实现,有具体的子类实现
     */
    @Override
    int interpret(Map<String, Integer> var) {
        return 0;
    }
}
/**
 * 加法表达式
 */
class AddExpression extends SymbolExpression {
    AddExpression(AbsExpression left, AbsExpression right) {
        super(left, right);
    }
    @Override
    public int interpret(Map<String, Integer> var) {
        return left.interpret(var) + right.interpret(var);
    }
}
/**
 * 减法表达式
 */
class SubExpression extends SymbolExpression {
    SubExpression(AbsExpression left, AbsExpression right) {
        super(left, right);
    }
    @Override
    public int interpret(Map<String, Integer> var) {
        return left.interpret(var) - right.interpret(var);
    }
}
/**
 * 计算器
 */
class Calculator {
    private AbsExpression expression;
    Calculator(String expStr) {
        Stack<AbsExpression> stack = new Stack<>();
        AbsExpression left, right;
        for (int i = 0; i < expStr.length(); i++) {
            switch (expStr.charAt(i)) {
                case '+':
                    left = stack.pop();
                    right = new VarExpression(String.valueOf(expStr.charAt(++i)));
                    stack.push(new AddExpression(left, right));
                    break;
                case '-':
                    left =  stack.pop();
                    right = new VarExpression(String.valueOf(expStr.charAt(++i)));
                    stack.push(new SubExpression(left, right));
                    break;
                default:
                    stack.push(new VarExpression(String.valueOf(expStr.charAt(i))));
                    break;
            }
        }
        this.expression = stack.pop();
    }
    public int run(Map<String, Integer> var) {
        return this.expression.interpret(var);
    }
}

解释器模式在Spring框架应用的源码剖析

  • Spring 框架中 SpelExpressionParser 就使用到解释器模式
public class InterpreterSpring {
    public static void main(String[] args) {
        //创建一个 Parser 对象
        SpelExpressionParser parser = new SpelExpressionParser();
        //通过 Parser 对象 获取到一个Expression对象
        //会根据不同的  Parser 对象 ,返回不同的 Expression对象
        Expression expression = parser.parseExpression("10 * (2 + 1) * 1 + 66");
        int result = (Integer) expression.getValue();
        System.out.println(result);
    }
}
	 ↓↓↓↓↓
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 doParseExpression(expressionString, null);
	}
	@Override
	protected SpelExpression doParseExpression(String expressionString, @Nullable ParserContext context) throws ParseException {
		return new InternalSpelExpressionParser(this.configuration).doParseExpression(expressionString, context);
	}
}
  • Expression是表达式接口,下面有不同的实现类,比如SpelExpression, 或者CompositeStringExpression。
  • 使用时候,根据你创建的不同的Parser 对象,返回不同的 Expression 对象。
    Java后端架构师的成长之路(二)——Java设计模式(3)_第27张图片
  • 使用得到的 Expression对象,调用getValue 解释执行 表达式,最后得到结果

解释器模式的注意事项和细节

  • 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性。
  • 应用场景:编译器、运算表达式计算、正则表达式、机器人等。
  • 使用解释器可能带来的问题:解释器模式会引起类膨胀、解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能降低。

状态模式

APP抽奖活动问题

请编写程序完成APP抽奖活动具体要求如下:

  • 假如每参加一次这个活动要扣除用户50积分,中奖概率是10%;
  • 奖品数量固定,抽完就不能抽奖;
  • 活动有四个状态:可以抽奖、不能抽奖、发放奖品和奖品领完;
  • 活动的四个状态转换关系图如下:
    Java后端架构师的成长之路(二)——Java设计模式(3)_第28张图片

状态模式基本介绍

  • 状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换。
  • 当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类。

状态模式原理类图

Java后端架构师的成长之路(二)——Java设计模式(3)_第29张图片

  • Context:为环境角色,用于维护 State 实例,这个实例定义当前状态
  • State:是抽象状态角色,定义一个接口封装,封装与 Context 的一个特定接口相关的行为
  • ConcreteState:具体的状态角色,每个子类实现一个与 Context 的一个状态相关的行为

状态模式解决APP抽奖问题

  • 思路分析:定义出一个接口叫状态接口,每个状态都实现它;接口有扣除积分方法、抽奖方法、发放奖品方法。
    Java后端架构师的成长之路(二)——Java设计模式(3)_第30张图片
  • 代码实现:
public class StateCase {
    public static void main(String[] args) {
        // 创建活动对象,奖品池有2个奖品
        RaffleActivity activity = new RaffleActivity(2);
        for (int i = 0; i < 100; i++) {
            System.out.println("-----第" + (i + 1) + "次抽奖-----");
            // 参加抽奖,第一步点击扣除积分
            activity.deductMoney();
            // 第二步抽奖
            activity.raffle();
        }
    }
}
/**
 * 抽象状态类
 */
abstract class State {
    /**
     * 扣除积分
     */
    abstract void deductMoney();
    /**
     * 是否抽中奖品
     */
    abstract boolean raffle();
    /**
     * 发放奖品
     */
    abstract void dispensePrize();
}
@Data
class RaffleActivity {
    /**
     * 表示活动当前的状态,是变化的
     */
    private State state;
    /**
     * 奖品数量
     */
    private int count;
    /**
     * 四个属性,表示四种状态
     */
    private State noRaffleState = new NoRaffleState(this);
    private State canRaffleState = new CanRaffleState(this);
    private State dispenseState = new DispenseState(this);
    private State dispenseOutState = new DispenseOutState(this);
    /**
     * 初始化当前的状态为 noRafflleState(即不能抽奖的状态)
     * 初始化奖品的数量
     */
    public RaffleActivity(int count) {
        this.state = getNoRaffleState();
        this.count = count;
    }
    /**
     * 扣分,调用当前状态为deductMoney
     */
    public void deductMoney() {
        state.deductMoney();
    }
    /**
     * 抽奖
     */
    public void raffle() {
        // 如果当前的状态是抽奖成功
        if (state.raffle()) {
            // 领取奖品
            state.dispensePrize();
        }
    }
    /**
     * 这里注意,每领取一次奖品,count--
     */
    public int getCount() {
        return count--;
    }
}
/**
 * 不能抽奖的状态
 */
class NoRaffleState extends State {
    private RaffleActivity activity;
    NoRaffleState(RaffleActivity activity) {
        this.activity = activity;
    }
    /**
     * 当前状态可以扣积分,扣除积分后,将状态设置为可以抽奖状态
     */
    @Override
    void deductMoney() {
        System.out.println("扣除50积分成功,您可以抽奖了");
        activity.setState(activity.getCanRaffleState());
    }
    /**
     * 当前在不能抽奖状态
     */
    @Override
    boolean raffle() {
        System.out.println("扣了积分才能抽奖哦!");
        return false;
    }
    @Override
    void dispensePrize() {
        System.out.println("不能发放奖品");
    }
}
/**
 * 可以抽奖的状态
 */
class CanRaffleState extends State {
    private RaffleActivity activity;
    CanRaffleState(RaffleActivity activity) {
        this.activity = activity;
    }
    /**
     * 已经扣除了积分,不能再扣
     */
    @Override
    void deductMoney() {
        System.out.println("已经扣除过积分了");
    }
    /**
     * 可以抽奖, 抽完奖后,根据实际情况,改成新的状态
     */
    @Override
    boolean raffle() {
        System.out.println("正在抽奖,请稍等");
        int num = new Random().nextInt(10);
        // 10%中奖机会
        if (num == 0) {
            // 改变活动状态为发放奖品
            activity.setState(activity.getDispenseState());
            return true;
        } else {
            System.out.println("很遗憾没有抽中奖品");
            activity.setState(activity.getNoRaffleState());
            return false;
        }
    }
    /**
     * 不能发放奖品
     */
    @Override
    void dispensePrize() {
        System.out.println("没中奖,不能发放奖品");
    }
}
/**
 * 发放奖品的状态
 */
class DispenseState extends State {
    private RaffleActivity activity;
    DispenseState(RaffleActivity activity) {
        this.activity = activity;
    }
    @Override
    void deductMoney() {
        System.out.println("不能扣除积分");
    }

    @Override
    boolean raffle() {
        System.out.println("不能抽奖");
        return false;
    }
    /**
     * 发放奖品
     */
    @Override
    void dispensePrize() {
        if (activity.getCount() > 0) {
            System.out.println("恭喜中奖了");
            // 改变状态为不能抽奖
            activity.setState(activity.getNoRaffleState());
        } else {
            System.out.println("很遗憾,奖品发放完了");
            // 改变状态为奖品发送完毕,后面就不能发放奖品了
            activity.setState(activity.getDispenseState());
        }
    }
}
/**
 * 奖品发放完毕的状态
 * 当我们 activity 改变成 DispenseOutState, 抽奖活动结束
 */
class DispenseOutState extends State {
    private RaffleActivity activity;
    DispenseOutState(RaffleActivity activity) {
        this.activity = activity;
    }
    @Override
    void deductMoney() {
        System.out.println("奖品发放完了,请下次再参加");
    }

    @Override
    boolean raffle() {
        System.out.println("奖品发放完了,请下次再参加");
        return false;
    }

    @Override
    void dispensePrize() {
        System.out.println("奖品发放完了,请下次再参加");
    }
}

状态模式在实际项目-借贷平台源码分析

  • 借贷平台的订单,有审核-发布-抢单等等步骤,随着操作的不同,会改变订单的状态, 项目中的这个模块实现就会使用到状态模式。
  • 通常通过if/else判断订单的状态,从而实现不同的逻辑,伪代码如下
    Java后端架构师的成长之路(二)——Java设计模式(3)_第31张图片
  • 使用状态模式完成 借贷平台项目的审核模块。
  • UML类图:
    Java后端架构师的成长之路(二)——Java设计模式(3)_第32张图片
  • 代码实现:
// TODO

状态模式的注意事项和细节

  • 代码有很强的可读性:状态模式将每个状态的行为封装到对应的一个类中。
  • 方便维护:将容易产生问题的if-else语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多if-else语句,而且容易出错。
  • 符合开闭原则:容易增删状态。
  • 会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度。
  • 应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式。

策略模式

鸭子问题

  • 有各种鸭子(比如 野鸭、北京鸭、水鸭等, 鸭子有各种行为,比如 叫、飞行等),显示鸭子的信息
  • 传统方案解决鸭子问题的分析:
    Java后端架构师的成长之路(二)——Java设计模式(3)_第33张图片
  • 代码实现:
abstract class Duck {
    abstract void display();
    void quack() {
        System.out.println("鸭子嘎嘎叫");
    }
    void swim() {
        System.out.println("鸭子会游泳");
    }
    void fly() {
        System.out.println("鸭子会飞翔");
    }
}
class WildDuck extends Duck {
    @Override
    void display() {
        System.out.println("-- 野鸭 --");
    }
}
class PekingDuck extends Duck {
    @Override
    void display() {
        System.out.println("-- 北京鸭 --");
    }
    @Override
    void fly() {
        System.out.println("北京鸭不能飞翔");
    }
}
class ToyDuck extends Duck {
    @Override
    void display() {
        System.out.println("-- 玩具鸭 --");
    }
    @Override
    void quack() {
        System.out.println("玩具鸭不会叫");
    }
    @Override
    void swim() {
        System.out.println("玩具鸭不会游泳");
    }
    @Override
    void fly() {
        System.out.println("玩具鸭不会飞");
    }
}
  • 其它鸭子,都继承了Duck类,所以fly让所有子类都会飞了,这是不正确的。
  • 上面说的1的问题,其实是继承带来的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分。会有溢出效应
  • 为了改进1问题,我们可以通过覆盖fly方法来解决=>覆盖解决
  • 问题又来了,如果我们有一个玩具鸭子ToyDuck, 这样就需要ToyDuck去覆盖Duck的所有实现的方法 => 解决思路 策略模式 (strategy pattern)

策略模式基本介绍

  • 策略模式(Strategy Pattern)中,定义算法族,分别封装起来,让他们之间可以相替换,此模式让算法的变化独立于使用算法的客户。
  • 这算法体现了几个设计原则:第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口);第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)。

策略模式的原理类图

Java后端架构师的成长之路(二)——Java设计模式(3)_第34张图片

  • Context(环境类):它是使用算法的角色,在解某个问题是,可以采用多种策略。
  • Strategy(抽象策略类):抽象策略类,为所支持的算法声明了抽象方法,是所有策略类的父类,可以是抽象类,也可以是接口。
  • ConcreteStrategy(具体策略类):实现了在抽象策略类中声明的算法,在运行时具体策略类会覆盖环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务功能。

策略模式解决鸭子问题

  • 思路分析:分别封装行为接口,实现算法族,超类里放行为接口对象,在子类里具体设定行为对象。原则就是分离变化部分,封装接口,基于接口编程各种功能。
    Java后端架构师的成长之路(二)——Java设计模式(3)_第35张图片
  • 代码实现:
/**
 * 飞翔行为:抽象策略类
 */
interface FlyBehavior {
    void fly();
}
/**
 * 下面是具体飞翔:具体策略类
 */
class GoodFlyBehavior implements FlyBehavior{
    @Override
    public void fly() {
        System.out.println("飞翔技术高超");
    }
}
class BadFlyBehavior implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("飞翔水平一般");
    }
}
class NoFlyBehavior implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("不会飞翔");
    }
}
/**
 * 叫声行为:抽象策略类
 */
interface QuackBehavor {
    void quack();
}
/**
 * 下面是具体叫声:具体策略类
 */
class GaGaQuackBehavor implements QuackBehavor {
    @Override
    public void quack() {
        System.out.println("嘎嘎叫");
    }
}
class GeGeQuackBehavor implements QuackBehavor {
    @Override
    public void quack() {
        System.out.println("咯咯叫");
    }
}
class NoQuackBehavor implements QuackBehavor {
    @Override
    public void quack() {
        System.out.println("不会叫");
    }
}
/**
 * 鸭子:环境类
 */
abstract class Duck {
    FlyBehavior flyBehavior;
    QuackBehavor quackBehavor;
    abstract void display();
    void quack() {
    }
    void fly() {
        if (flyBehavior != null) {
            flyBehavior.fly();
        }
    }
}
class WildDuck extends Duck {
    WildDuck() {
        flyBehavior = new GoodFlyBehavior();
        quackBehavor = new GeGeQuackBehavor();
    }
    @Override
    void display() {
        System.out.println("-- 野鸭 --");
    }
}
class PekingDuck extends Duck {
    PekingDuck() {
        flyBehavior = new BadFlyBehavior();
        quackBehavor = new GaGaQuackBehavor();
    }
    @Override
    void display() {
        System.out.println("-- 北京鸭子 --");
    }
}
class ToyDuck extends Duck {
    ToyDuck() {
        flyBehavior = new NoFlyBehavior();
        quackBehavor = new NoQuackBehavor();
    }
    @Override
    void display() {
        System.out.println("-- 玩具鸭子 --");
    }
}

策略模式在JDK-Arrays 应用的源码分析

  • JDK的 Arrays 的 Comparator 就使用了策略模式
public class JDKStrategy {
    public static void main(String[] args) {
        Integer[] data = {9, 1, 2, 8, 4, 3};
        // 实现了 Comparator 接口(策略接口) , 匿名类 对象 new Comparator(){..}
        // 对象 new Comparator(){..} 就是实现了 策略接口 的对象
        // public int compare(Integer o1, Integer o2){} 指定具体的处理方式
        Comparator<Integer> comparator = (o1, o2) -> (o1 > o2) ? 1 : -1;
        Arrays.sort(data, comparator);
        System.out.println(Arrays.toString(data));
    }
}
	↓↓↓↓↓
@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
    //...省略
}
	↓↓↓↓↓
public static <T> void sort(T[] a, Comparator<? super T> c) {
    if (c == null) {
    	// 就按照自己的方法排序
        sort(a);
    } else {
        if (LegacyMergeSort.userRequested)
        	// 按照这个方式调用c
            legacyMergeSort(a, c);
        else
        	// 按照这个方式调用c比较器
            TimSort.sort(a, 0, a.length, c, null, 0, 0);
    }
}

策略模式的注意事项和细节

  • 策略模式的关键是:分析项目中变化部分与不变部分。
  • 策略模式的核心思想是:多用组合/聚合少用继承;用行为类组合,而不是行为的继承,更有弹性。
  • 体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可,避免了使用多重转移语句(if…else if…else)。
  • 提供了可以替换继承关系的办法:策略模式将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展。
  • 需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大

责任链模式

OA系统采购审批需求

采购员采购教学器材:

  • 如果金额小于等于5000,由教学主任审批(0<=x<=5000);
  • 如果金额小于等于10000,由院长审批(5000
  • 如果金额小于等于30000,由副校长审批(10000
  • 如果金额超过30000以上,有校长审批(30000

传统方案解决OA系统审批问题分析

  • 传统方式是:接收到一个采购请求后,根据采购金额来调用对应的Approver (审批人)完成审批。
  • 传统方式的问题分析:客户端这里会使用到分支判断(比如 switch) 来对不同的采购请求处理, 这样就存在如下问题:
* 如果各个级别的人员审批金额发生变化,在客户端的也需要变化
* 客户端必须明确的知道有多少个审批级别和访问
  • 这样对一个采购请求进行处理和Approver(审批人)就存在强耦合关系,不利于代码的扩展和维护,解决方案 => 职责链模式。

职责链模式基本介绍

  • 职责链模式(Chain of Responsibility Pattern),又叫责任链模式,为请求创建了一个接收者对象的链(简单示意图)。这种模式对请求的发送者和接收者进行解耦。
  • 职责链模式通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
    Java后端架构师的成长之路(二)——Java设计模式(3)_第36张图片

职责链模式的原理类图

Java后端架构师的成长之路(二)——Java设计模式(3)_第37张图片

  • Handler:抽象的处理者, 定义了一个处理请求的接口,同时含义另外一个 Handler 对象。
  • ConcreteHandlerA , B 是具体的处理者, 处理它自己负责的请求, 可以访问它的后继者(即下一个处理者),如果可以处理当前请求,则处理,否则就将该请求交个后继者去处理,从而形成一个职责链。
  • Request , 含义很多属性,表示一个请求。
  • 职责链模式(Chain Of Responsibility)使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

职责链模式解决OA系统采购审批

  • 思路分析:
    Java后端架构师的成长之路(二)——Java设计模式(3)_第38张图片
  • 代码实现:
public class ChainOfResponsibilityCase {
    public static void main(String[] args) {
        // 创建一个请求
        PurchaseRequest request = new PurchaseRequest(1, 31000, 1);
        // 创建各个级别的审批人
        DepartmentApprover departmentApprover = new DepartmentApprover();
        CollegeApprover collegeApprover = new CollegeApprover();
        ViceShcoolMasterApprover viceShcoolMasterApprover = new ViceShcoolMasterApprover();
        ShcoolMasterApprover shcoolMasterApprover = new ShcoolMasterApprover();
        // 将各个审批级别的下一个审批人设置好,形成环
        departmentApprover.setApprover(collegeApprover);
        collegeApprover.setApprover(viceShcoolMasterApprover);
        viceShcoolMasterApprover.setApprover(shcoolMasterApprover);
        shcoolMasterApprover.setApprover(departmentApprover);

        System.out.println("----开始从教学主任处理----");
        departmentApprover.processRequest(request);
        System.out.println("----开始从副校长处理----");
        viceShcoolMasterApprover.processRequest(request);
    }
}
/**
 * 请求类
 */
@Data
@AllArgsConstructor
class PurchaseRequest {
    private int type;
    private float price;
    private int id;
}
/**
 * 审批人
 */
abstract class Approver {
    /**
     * 下一个处理者
     */
    Approver approver;
    String name;
    Approver(String name) {
        this.name = name;
    }
    public void setApprover(Approver approver) {
        this.approver = approver;
    }
    abstract void processRequest(PurchaseRequest request);
}
/**
 * 下面是具体的审批人
 */
class DepartmentApprover extends Approver {
    DepartmentApprover() {
        super("教学主任");
    }
    @Override
    void processRequest(PurchaseRequest request) {
        if (request.getPrice() <= 5000) {
            System.out.println("采购单 id = " + request.getId() + " 被 " + this.name + " 审批");
        } else {
            System.out.println(this.name + " 审批不了");
            approver.processRequest(request);
        }
    }
}
class CollegeApprover extends Approver {
    CollegeApprover() {
        super("院长");
    }
    @Override
    void processRequest(PurchaseRequest request) {
        if (request.getPrice() > 5000 && request.getPrice() <= 10000) {
            System.out.println("采购单 id = " + request.getId() + " 被 " + this.name + " 审批");
        } else {
            System.out.println(this.name + " 审批不了");
            approver.processRequest(request);
        }
    }
}
class ViceShcoolMasterApprover extends Approver {
    ViceShcoolMasterApprover() {
        super("副校长");
    }
    @Override
    void processRequest(PurchaseRequest request) {
        if (request.getPrice() > 10000 && request.getPrice() <= 30000) {
            System.out.println("采购单 id = " + request.getId() + " 被 " + this.name + " 审批");
        } else {
            System.out.println(this.name + " 审批不了");
            approver.processRequest(request);
        }
    }
}
class ShcoolMasterApprover extends Approver {
    ShcoolMasterApprover() {
        super("校长");
    }
    @Override
    void processRequest(PurchaseRequest request) {
        if (request.getPrice() > 30000) {
            System.out.println("采购单 id = " + request.getId() + " 被 " + this.name + " 审批");
        } else {
            System.out.println(this.name + " 审批不了");
            approver.processRequest(request);
        }
    }
}
//----开始从教学主任处理----
//教学主任 审批不了
//院长 审批不了
//副校长 审批不了
//采购单 id = 1 被 校长 审批
//----开始从副校长处理----
//副校长 审批不了
//采购单 id = 1 被 校长 审批

职责链模式在SpringMVC的源码分析

SpringMVC-HandlerExecutionChain 类就使用到职责链模式
Java后端架构师的成长之路(二)——Java设计模式(3)_第39张图片

  • springmvc 请求的流程图中,执行了 拦截器相关方法 interceptor.preHandler 等等。
  • 在处理SpringMvc请求时,除了使用到职责链模式,还使用到适配器模式。
  • HandlerExecutionChain 主要负责的是请求拦截器的执行和请求处理,但是他本身不处理请求,只是将请求分配给链上注册处理器执行,这是职责链实现方式,减少职责链本身与处理逻辑之间的耦合,规范了处理流程。
  • HandlerExecutionChain 维护了 HandlerInterceptor 的集合, 可以向其中注册相应的拦截器。

职责链模式的注意事项和细节

  • 将请求和处理分开,实现解耦,提高系统的灵活性。
  • 简化了对象,使对象不需要知道链的结构。
  • 性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在Handler中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能。
  • 调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂。
  • 最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪等审批流程、Java Web中Tomcat对Encoding的处理、拦截器。

设计模式在框架或项目中源码分析的说明

  • 设计模式是程序员在编程中,有意或者是无意使用到的(也不所有的程序员都学习过设计模式),并且同一种设计模式实现方式也不是100%的一样,设计模式主要是提高程序的扩展性,可读性,可维护性、规范性。
  • 所以在我们讲某个设计模式在源码框架中使用时,和我们的标准的设计模式写法可能会有些出入,比如组合模式 Component 可以是抽象类,接口,也可以是一个实现类, 我们讲源码时(JDK HashMap源码),Component 就可能不一样。
  • 对于框架源码,源码中部分使用了A设计模式,还部分使用了B设计模式,也是有可能的,也就是说设计模式是可以结合使用的。
  • 因为设计模式主要是一种编程思想,既然是思想,具体实现方式,就不可能100%的一样(当然,程序的设计结构基本是一样的)。
  • 所以,提醒大家我们学习设计模式时,(包括看源码分析),要抓住本质,就是使用这个设计模式到底带了了什么好处? 是 扩展性提高了,还是更加规范了,这样我们才能领会设计模式的精妙之处。

你可能感兴趣的:(设计模式,行为型设计模式)