文章目录
14. 模板方法模式
14.1 需求的引入
豆浆制作问题
编写制作豆浆的程序,说明如下:
- 制作豆浆的流程 选材—>添加配料—>浸泡—>放到豆浆机打碎
- 通过添加不同的配料,可以制作出不同口味的豆浆
- 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的
- 请使用 模板方法模式 完成 (说明:因为模板方法模式,比较简单,很容易就想到这个方案,因此就直接使用, 不再使用传统的方案来引出模板方法模式 )
14.2 基本介绍
基本介绍
- 模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),z 在一个抽象类公开定义了执行 它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
- 简单说,模板方法模式 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一 个算法的结构,就可以重定义该算法的某些特定步骤
- 这种类型的设计模式属于行为型模式
模板方法模式的角色及职责
- AbstractClass 抽象类, 类中实现了模板方法(template),定义了算法的骨架,具体子类需要去实现其它的抽象方法 。
- ConcreteClass 实现抽象方法以完成算法中特点子类的步骤。
14.3 应用实例
/**
*
* 模板方法模式
*
* @author houyu
* @createTime 2020/1/2 20:14
*/
public class Demo {
public static void main(String[] args) {
SoyaMilk soyaMilk = new RedBeanSoyaMilk();
soyaMilk.mark();
/*
* 选择上好的红豆
* 浸泡30分钟
* 打碎
*/
}
/**
* 抽象类
*/
public static abstract class SoyaMilk {
/**
* 模板方法, 一般定义为final, 防止子类重写
*/
public final void mark() {
select();
addCondiments();
soak();
beat();
}
/**
* 抽象方法, 子类实现
*/
protected abstract void select();
/**
* 钩子方法, 默认空实现 ( 子类可以视情况要不要覆盖它 )
*/
private void addCondiments() {
}
/**
* 抽象方法, 子类实现
*/
protected abstract void soak();
/**
* 抽象方法, 子类实现
*/
protected abstract void beat();
}
/**
* 具体子类, 实现具体方法(处理逻辑)
*/
public static class RedBeanSoyaMilk extends SoyaMilk {
@Override
protected void select() {
System.out.println("选择上好的红豆");
}
@Override
protected void soak() {
System.out.println("浸泡30分钟");
}
@Override
protected void beat() {
System.out.println("打碎");
}
}
}
14.4 模板方法模式在Spring框架应用的源码分析
- ConfigurableApplicationContext.refresh() 定义接口
void refresh() throws BeansException, IllegalStateException;
- AbstractApplicationContext.refresh() 为模板方法
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
- AbstractApplicationContext.obtainFreshBeanFactory() 方法
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
return getBeanFactory();
}
- AbstractApplicationContext.refreshBeanFactory() 抽象方法
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
- AbstractApplicationContext.getBeanFactory() 抽象方法
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
- AbstractApplicationContext.onRefresh() 钩子方法, 空实现
protected void onRefresh() throws BeansException {
// For subclasses: do nothing by default.
}
14.5 模板方法模式的注意事项和细节
- 基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
- 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。
- 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。
- 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
- 一般模板方法都加上final关键字,防止子类重写模板方法.
- 模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤,这一系列的步骤基本相同,但其个别步骤在实现时可能不同,通常考虑用模板方法模式来处理
15. 命令模式
15.1 需求的引入
智能生活项目需求
- 我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装app就可以控制对这些家电工作。
- 这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个App,分别控制,我们希望只要一个app就可以控制全部智能家电。
- 要实现一个app控制所有智能家电的需要,则每个智能家电厂家都要提供一个统一的接口给app调用,这时就可以考虑使用命令模式。
- 命令模式可将“动作的请求者”从“动作的执行者”对象中解耦出来.
- 在我们的例子中,动作的请求者是手机app,动作的执行者是每个厂商的一个家电产品
15.2 基本介绍
命令模式基本介绍
- 命令模式(CommandPattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计
- 命名模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
- 在命名模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作。
- 通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。Invoker是调用者(将军),Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,持有接收对象
- 命令模式的原理类图
- 对原理类图的说明-即(命名模式的角色及职责)
- Receiver: 接受者角色,知道如何实施和执行一个请求相关的操作
- Command: 命令角色,需要执行的所有命令都在这里,可以是接口或抽象类
- ConcreteCommand: 具体命令角色, 将一个接受者对象与一个动作绑定,调用接受者相应的操作,实现execute
- Invoker: 调用者角色
15.3 应用实例
import java.util.HashMap;
import java.util.Map;
/**
* @author houyu
* @createTime 2020/1/2 22:38
*/
public class Demo {
public static void main(String[] args) {
// 1. 创建接受者
LightReceiver lightReceiver = new LightReceiver();
// 2. 创建命令
LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);
LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver);
// 3. 创建控制器
InvokerController controller = new InvokerController();
// 4. 控制器添加命令
controller.addOnCommand("light", lightOnCommand);
controller.addOffCommand("light", lightOffCommand);
// 5. 使用
controller.pushedOnButton("light");
controller.pushedOffButton("light");
controller.cancel();
/*
* 电灯打开了
* 电灯关闭了
* 电灯打开了
*/
}
/**
* 接受者
*/
public static class LightReceiver {
public void on() {
System.out.println("电灯打开了");
}
public void off() {
System.out.println("电灯关闭了");
}
}
/**
* 命令接口
*/
public interface Command {
/** 执行动作(操作) */
public void execute();
/** 撤销动作(操作) */
public void undo();
}
/**
* 具体命令(电灯开,命令)
*/
public static class LightOnCommand implements Command {
/** 聚合 接受者(执行者) */
private LightReceiver lightReceiver;
public LightOnCommand(LightReceiver lightReceiver) {
this.lightReceiver = lightReceiver;
}
@Override
public void execute() {
lightReceiver.on();
}
@Override
public void undo() {
lightReceiver.off();
}
}
/**
* 具体命令(电灯关,命令)
*/
public static class LightOffCommand implements Command {
/** 聚合 接受者(执行者) */
private LightReceiver lightReceiver;
public LightOffCommand(LightReceiver lightReceiver) {
this.lightReceiver = lightReceiver;
}
@Override
public void execute() {
lightReceiver.off();
}
@Override
public void undo() {
lightReceiver.on();
}
}
/**
* 空命令, 用于避免null判断, 方法空实现
*/
public static class NoCommand implements Command {
@Override
public void execute() {
}
@Override
public void undo() {
}
}
/**
* 调用者 (执行控制器)
*/
public static class InvokerController {
/** 开 组 */
private Map<String, Command> onMap = new HashMap<>(8);
/** 关 组 */
private Map<String, Command> offMap = new HashMap<>(8);
/** 空命令 */
private static final NoCommand NO_COMMAND = new NoCommand();
/** 最后一个命令, 用于撤销 */
private Command lastCommand = NO_COMMAND;
public void addOnCommand(String type, Command on) {
onMap.put(type, on);
}
public void addOffCommand(String type, Command off) {
offMap.put(type, off);
}
public void pushedOnButton(String type) {
Command command = onMap.getOrDefault(type, NO_COMMAND);
command.execute();
lastCommand = command;
}
public void pushedOffButton(String type) {
Command command = offMap.getOrDefault(type, NO_COMMAND);
command.execute();
lastCommand = command;
}
/**
* 撤销操作
*/
public void cancel() {
lastCommand.undo();
}
}
}
15.4 命令模式在Spring框架JdbcTemplate应用的源码分析
- JdbcTemplate.query(final String sql, final ResultSetExtractor rse) throws DataAccessException
- 注意 接口 StatementCallback 【命令接口, 命令方法是 doInStatement() 】
- 注意 具体实现 StatementCallback 的 QueryStatementCallback 【内部类, 充当了具体的命令以及接受者(执行者) 】
- 注意 内部调用的execute()
@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 + "]");
}
/**
* Callback to execute the query.
*/
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());
}
- JdbcTemplate.execute(StatementCallback action)
方法内部 execute() 调用 QueryStatementCallback 具体实例的 action.doInStatement(stmt);
@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());
}
}
- org.springframework.jdbc.core.StatementCallback [ StatementCallback接口,类似命令接口(Command) ]
@FunctionalInterface
public interface StatementCallback<T> {
@Nullable
T doInStatement(Statement stmt) throws SQLException, DataAccessException;
}
- JdbcTemplate.query().QueryStatementCallback [ 匿名内部类,实现了命令接口,充当了具体的命令以及接受者(执行者) ]
class QueryStatementCallback implements StatementCallback<T>, SqlProvider
命令接口(在Jdbc Template中有4个内部类实现该接口,充当了具体命令角色, 也充当了命令的执行者(接受者)
命令调用者是JdbcTemplate,其中execute(StatementCallbackaction)方法中,调用action.doInStatement方法, 不同的实现StatementCallback接口的对象,对应不同的doInStatemnt实现逻辑, 另外实现StatementCallback命令接口的子类还有QueryStatementCallback等。
15.5 命令模式的注意事项和细节
- 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:”请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。
- 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令
- 容易实现对请求的撤销和重做
- 命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在使用的时候要注意
- 空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。
- 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟CMD(DOS命令)订单的撤销/恢复、触发-反馈机制