模板方法模式-简单实现和Spring中的使用分析

设计模式-总览icon-default.png?t=LBL2https://mp.csdn.net/mp_blog/creation/editor/122202507目录

一、模板方法模式

1、父类定义骨架

2、子类实现特殊的处理

3、测试调用

4、执行结果

二、Spring中的模板方法模式分析

1、AbstractApplicationContext最重要的refresh方法使用模板方法模式实现

2、SpringJDBC使用模板方法模式实现


模板方法模式应该在项目中的使用会比较广泛,父类定义了骨架(调用哪些方法及顺序),某些特定方法由子类实现

好处:代码复用,减少重复代码。除了子类要实现的特定方法,其他方法及方法调用顺序都在父类中预先写好

子类很多时候需要特殊处理的方法,需要覆盖父类方法,分为两种(这个在项目中非常的适用):

  1、抽象方法:父类中的是抽象方法,子类必须覆盖

  2、钩子方法:父类中是一个空方法,子类继承了默认也是空的

还是先来一个简单的例子看看

一、模板方法模式

来自《Head First设计模式》中的例子(具体可以参见:github地址),这个例子觉得还是举的非常恰当的,冲泡咖啡和茶的时候大致工序都差不多,只是某些实现的具体细节不同而已。

1、父类定义骨架

    父类中需要定义所有的模板方法,并且定义需要执行的流程。泡茶或者咖啡都需要有以下步骤:

1)、将水煮沸

2)、冲泡(需要子类实现),   因为茶或者咖啡温度等不太一样,比如茶一般需要先加少量水进行冲泡

3)、将水倒入杯中

4)、添加调味料(需要子类实现)   比如柠檬茶则还需要加入一些柠檬水或者柠檬

/**
 *   模板基类(咖啡或茶的冲泡步骤模板)
 *
 * @author lihongmin
 * @date 2018/9/3 22:33
 */
public abstract class CaffeineBeverage {

    private static final boolean DEFAULT_ADD_CONDIMENTS = true;

    /**
     *  不允许改变{咖啡或茶的冲泡}执行步骤
     */
    final void prepareRecipe() {

        boilWater();

        brew();

        pourInCup();

        /**
         *  设置一个回调钩子
         */
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }

    /**
     *  冲泡(需要子类实现)
     */
    abstract void brew();

    /**
     *  添加调味料(需要子类实现)
     */
    abstract void addCondiments();

    /**
     *  将水煮沸
     */
    void boilWater() {
        System.out.println("将水煮沸!");
    }

    /**
     *  将水倒入杯中
     */
    void pourInCup() {
        System.out.println("将沸腾的水倒入杯中!");
    }

    boolean customerWantsCondiments() {
        return DEFAULT_ADD_CONDIMENTS;
    }
}

2、子类实现特殊的处理

public class Coffee extends CaffeineBeverage {


    public Coffee() {
        super.prepareRecipe();
    }

    @Override
    public void brew() {

        System.out.println("咖啡需要直接泡!");
    }

    @Override
    public void addCondiments() {

        System.out.println("咖啡需要白开水等!");
    }
}
public class Tea extends CaffeineBeverage {
    
    public Tea() {
        super.prepareRecipe();
    }

    @Override
    public void brew() {

        System.out.println("泡茶需要将80度左右的水将茶叶泡开了!");
    }

    @Override
    public void addCondiments() {

        System.out.println("泡茶需要柠檬水等!");
    }

}

3、测试调用

public class TestTemplateMethod {

    public static void main(String[] args) {
        System.out.println("tea ......");
        Tea tea = new Tea();
        System.out.println("tea is OK !");

        System.out.println("---------------------------------------------");

        System.out.println("tea ......");
        Coffee coffee = new Coffee();
        System.out.println("tea is OK !");
    }
}

4、执行结果

tea ......
将水煮沸!
泡茶需要将80度左右的水将茶叶泡开了!
将沸腾的水倒入杯中!
泡茶需要柠檬水等!
tea is OK !
---------------------------------------------
coffee ......
将水煮沸!
咖啡需要直接泡!
将沸腾的水倒入杯中!
咖啡需要白开水等!
coffee is OK !

思考:其实项目里面可能用到模板的地方还是很多的,比如

1、商品系统,创建商品时(一个spu对应多个sku,并且spu和sku其实处理方式都差不多)。

2、订单系统,创建线上或者线下订单(大致流程差不多,具体实现有些不同,或者省去某些步骤)

例子还是很多的,只要有差不多的流程都可能用到模板方法模式。

二、Spring中的模板方法模式分析

1、AbstractApplicationContext最重要的refresh方法使用模板方法模式实现

Spring 的ApplicationContext的最重要的几个实现类型:ClassPathXmlApplicationContext、AnnotationConfigApplicationContext、AnnotationConfigWebApplicationContext等,初始化都调用父类AbstractApplicationContext的refresh()模板方法。

父类中定义了refresh的整个执行流程,但是我没想通为什么refresh方法没有加 final 处理。如下:

@Override
	public void refresh() throws BeansException, IllegalStateException {
		// startupShutdownMonitor为刷新和销毁的公共锁对象
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			// 1、准备刷新的上下文,系统属性以及环境变量等
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			// 2、始化BeanFactory并进行XML文件的读取, ApplicationContext包含了XmlBeanFactory的所有过程,该方法就会包含其中的
			// 读取配置类型,根据Xml获取和解析bean的配置信息等
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			// 3、对BeanFactory的功能进行补充,都是我们进行扩展的点需要好好熟悉,强大的功能来自于此,我靠,太多了。。。
			// 空了再好好研究吧, Aware 的相关接口实现的初始化
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				// 4、子类覆盖方法做额外的处理,不实现则可以看到的是protected的空方法
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				// 5、激活各种beanFactory处理器 ,容器级别的配置, invokeBeanFactoryPostProcessors(beanFactory)
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				// 6、注册拦截Bean创建Bean的处理器,这里只是注册,真正的调用是getBean, 依赖于上一步的处理
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				// 7、为上下文初始化message源,国际化支持
				initMessageSource();

				// Initialize event multicaster for this context.
				// 8、初始化应用消息广播,并放入 applicationEventMulticaster 的Bean中
				// 监听,观察者模式。我们可以实现ApplicationListener,使用监听回调通知
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				// 9、留给子类初始化其他的Bean , 里面居然是空方法
				onRefresh();

				// Check for listener beans and register them.
				// 10、在所有注册的Bean中查找Listener的Bean, 注册到消息广播器中
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				// 11、初始化剩下的非惰性单实例, 厉害了,都在这里的最后一步
				// DefaultListableBeanFactory.preInstantiateSingletons方法
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				// 12、完成刷新过程,通知生命周期处理器,lifecycleProcessor 刷新过程,同时发出ContextRefreshEvent 通知别人
				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.
				// 13、发生异常需要, 销毁已经创建的单例,以避免悬空资源
				destroyBeans();

				// Reset 'active' flag.
				// 14、重置 active 属性标识
				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...
				// finally中需要,在Spring的内核中重置常见的内省缓存 可能再也不需要单例bean的元数据了……
				resetCommonCaches();
			}
		}
	}

分析: postProcessBeanFactory(beanFactory)就是放父类进行覆盖的 protected 空方法

ClassPathXmlApplicationContext类型的容器,没有对这一个步骤有特殊定制,直接调用父类AbstractApplicationContext的空方法

所有web类型的则会实现自己的方法,如 XmlWebApplicationContext 或者 AnnotationConfigWebApplicationContext 都继承自己父类 AbstractRefreshableWebApplicationContext 中的重写postProcessBeanFactory方法

protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, 
            this.servletConfig));
        beanFactory.ignoreDependencyInterface(ServletContextAware.class);
        beanFactory.ignoreDependencyInterface(ServletConfigAware.class);
        WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, 
            this.servletContext);
        WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, 
            this.servletContext, this.servletConfig);
    }
 

其中 prepareRefresh()、prepareBeanFactory()、onRefresh()、finishRefresh()步骤基本都使用AbstractApplicationContext中的方法,处理 ReactiveWebServerApplicationContextServletWebServerApplicationContext 会实现自己的上述方法

比如finishRefresh()方法,父类中实现为:

protected void finishRefresh() {
        this.clearResourceCaches();
        this.initLifecycleProcessor();
        this.getLifecycleProcessor().onRefresh();
        this.publishEvent((ApplicationEvent)(new ContextRefreshedEvent(this)));
        LiveBeansView.registerApplicationContext(this);
    }

ServletWebServerApplicationContext 中的实现为:

protected void finishRefresh() {
    super.finishRefresh();
    WebServer webServer = this.startWebServer();
    if (webServer != null) {
        this.publishEvent(new ServletWebServerInitializedEvent(webServer, this));
    }
}

2、SpringJDBC使用模板方法模式实现

我们可以直接看一下SpringTemplate中的query(final String sql, final ResultSetExtractor<T> rse)方法,过个重载方法都会执行到:

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 + "]");
        }

        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;
            }
        }

        return this.execute((StatementCallback)(new QueryStatementCallback()));
    }

这里先在预制回调函数doInPreparedStatement,

真正的查找在execute()中:

@Nullable
    public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) 
throws DataAccessException {
        Assert.notNull(psc, "PreparedStatementCreator must not be null");
        Assert.notNull(action, "Callback object must not be null");
        if (this.logger.isDebugEnabled()) {
            String sql = getSql(psc);
            this.logger.debug("Executing prepared SQL statement" + (sql != null ? " [" 
                + sql + "]" : ""));
        }
        // 1、获取链接
        Connection con = DataSourceUtils.getConnection(this.obtainDataSource());
        PreparedStatement ps = null;

        Object var13;
        try {
            // 2、获取statement
            ps = psc.createPreparedStatement(con);
            // 3、获取resultset
            this.applyStatementSettings(ps);
            // 4、遍历resultset并封装成集合
            T result = action.doInPreparedStatement(ps);
            // 5、异常处理
            this.handleWarnings((Statement)ps);
            var13 = result;
        } catch (SQLException var10) {
            if (psc instanceof ParameterDisposer) {
                ((ParameterDisposer)psc).cleanupParameters();
            }

            String sql = getSql(psc);
            psc = null;
            JdbcUtils.closeStatement(ps);
            ps = null;
            DataSourceUtils.releaseConnection(con, this.getDataSource());
            con = null;
            throw this.translateException("PreparedStatementCallback", sql, var10);
        } finally {
            // 6、依次关闭connection,statement等资源
            if (psc instanceof ParameterDisposer) {
                ((ParameterDisposer)psc).cleanupParameters();
            }
            
            JdbcUtils.closeStatement(ps);
            DataSourceUtils.releaseConnection(con, this.getDataSource());
        }

        return var13;
    }

如上所述,模板方法流程大致与 jdbc一致,分别为:

// 1、获取链接
// 2、获取statement
// 3、获取resultset
// 4、遍历resultset并封装成集合
T result = action.doInPreparedStatement(ps);
// 5、异常处理

// 6、依次关闭connection,statement等资源

该模板执行流程中,除了第4步可能会发生变化,并且在该步骤回调了之前预制的回调方法,按照上面Spring AbstractApplicationContext中的,那么子类不同的处理需要各自重新自己的doInPreparedStatement()方法  也就是说我们可以写一个自己的 UserJdbcTemplate并重新改方法,但是Spring Jdbc实现的比较优雅,使用 RowCallbackHandler的时候又进行了一次钩子回调,我们再进行分析,如下:

我们的调用方式如下:

User user = new User();
        jdbcTemplate.query("select * from user where id = " + id, new RowCallbackHandler() {
            @Override
            public void processRow(ResultSet resultSet) throws SQLException {
                user.setId(resultSet.getLong("id"));
                user.setName(resultSet.getString("name"));
            }
        });
        return user;

我们往下继续:

public void query(String sql, RowCallbackHandler rch) throws DataAccessException {
        this.query((String)sql, (ResultSetExtractor)(new JdbcTemplate.RowCallbackHandlerResultSetExtractor(rch)));
    }

又回到上面的 SpringTemplate中的query(final String sql, final ResultSetExtractor<T> rse)方法,我们再看query里面的doInPreparedStatement回调方法发现其本身就是一个模板方法,

public T doInStatement(Statement stmt) throws SQLException {
    ResultSet rs = null;

    Object var3;
    try {
        // 1、执行查询
        rs = stmt.executeQuery(sql);
        // 2、执行额外的操作
        var3 = rse.extractData(rs);
    } finally {
        // 3、关闭ResultSet
        JdbcUtils.closeResultSet(rs);
    }
    return var3;
}

模板方法执行流程如下: 

// 1、执行查询
// 2、执行额外的操作
// 3、关闭ResultSet

其中1和2步骤都可能存在变化,比如Hikari数据库连接池的 HikariProxyCallableStatement 就重写了第一步,而就在第二步回调了预制的 我们写的代码的回调函数 processRow

总结:Spring JdbcTemplate的query方法总体结构是一个模板方法 + 回调函数,query方法中调用的execute(StatementCallback<T> action) 是一个模板方法,而预制的回调 doInStatement(Statement var1) 方法也是一个模板方法

<think>好的,用户想了解模板方法设计模式的原理与实现。首先,我需要回忆模板方法模式的基本概念。根据引用[3],模板方法模式定义了一个操作中的算法框架,将一些步骤推迟到子类中实现。这样可以在不改变算法结构的情况下,让子类重新定义某些步骤。 接下来,用户可能想知道原理部分。根据引用[4],模板方法模式通过抽象类中的模板方法规定算法结构执行顺序,具体步骤由子类实现。这意味着父类控制流程,子类负责具体实现。需要解释这种分层的设计如何提高代码复用,并允许子类扩展特定步骤。 然后是结构部分。通常模板方法模式包含抽象类具体子类。抽象类中的模板方法是final的,避免子类修改算法结构,而抽象方法由子类实现。比如引用[5]中的prepare_recipe()方法作为模板方法,定义了制作饮料的步骤,具体步骤如brew()addCondiments()由子类实现实现方式方面,需要给出代码示例。比如制作咖啡茶的例子,抽象类定义模板方法,子类实现具体步骤。根据引用[2],咖啡茶的冲泡过程有相似的步骤,可以抽象到父类,不同步骤由子类覆盖。 应用场景部分,引用[3]提到当多个类有相同的方法但某些步骤不同时适用。例如数据处理流程、文档生成等。需要举几个例子说明适用情况。 可能还需要对比其他模式,比如策略模式。根据引用[1],策略模式使用组合来改变整个算法,而模板方法使用继承来改变部分步骤。这点需要区分清楚,避免用户混淆。 最后,总结优缺点。优点包括代码复用、扩展性,缺点则是子类必须遵循框架,可能不够灵活,如引用[1]提到可能限制子类灵活性。需要客观说明适用场景潜在限制。 检查是否覆盖了用户需求:定义、原理、结构、实现、应用场景、优缺点。确保引用正确,每个引用对应的内容是否准确。比如引用[5]中的prepare_recipe作为模板方法的例子,引用[4]中的算法框架定义。确保回答结构清晰,分点明确,代码示例正确使用$$格式,行内公式用$符号。</think>### 模板方法设计模式的原理与实现 #### 一、定义与核心思想 模板方法模式(Template Method Pattern)是一种**行为型设计模式**,其核心思想是**定义一个算法的框架,允许子类在不改变算法结构的前提下重写某些步骤**。该模式通过抽象类实现,包含两类方法: 1. **模板方法**:定义算法骨架的固定流程,通常为`final`方法以防止子类修改[^3][^4]。 2. **抽象方法**:由子类实现的步骤,用于扩展或定制算法细节[^5]。 例如,制作咖啡茶的流程可抽象为“烧水→冲泡→添加调料”的固定框架,而具体的冲泡调料添加由子类实现[^2]。 --- #### 二、核心原理 1. **算法骨架固定化** 抽象类中通过模板方法规定执行顺序(如`prepare_recipe()`),子类仅需实现差异化的步骤(如`brew()``addCondiments()`)。 $$ \text{模板方法} = \text{固定步骤} + \text{抽象方法} $$ 2. **好莱坞原则(Hollywood Principle)** “不要调用我们,我们会调用你”——父类控制流程,子类被动响应具体步骤的调用。 --- #### 三、代码实现示例 以饮料制作为例,抽象类定义模板方法,子类实现具体步骤: ```java // 抽象类定义模板方法 abstract class CaffeineBeverage { // 模板方法(final防止子类修改结构) public final void prepareRecipe() { boilWater(); brew(); pourInCup(); addCondiments(); } // 公共步骤直接实现 void boilWater() { System.out.println("烧水"); } void pourInCup() { System.out.println("倒入杯子"); } // 子类必须实现的抽象方法 abstract void brew(); abstract void addCondiments(); } // 具体子类:咖啡 class Coffee extends CaffeineBeverage { void brew() { System.out.println("冲泡咖啡粉"); } void addCondiments() { System.out.println("加糖牛奶"); } } // 具体子类:茶 class Tea extends CaffeineBeverage { void brew() { System.out.println("浸泡茶叶"); } void addCondiments() { System.out.println("加柠檬"); } } ``` --- #### 四、应用场景 1. **流程标准化**:多个类有相同流程但部分步骤不同(如数据处理、文档生成)[^3]。 2. **框架设计**:框架定义主流程,用户通过子类扩展细节(如Spring的`JdbcTemplate`)。 3. **避免代码重复**:将公共代码提升到父类,差异化代码下放至子类[^2]。 --- #### 五、优缺点分析 | **优点** | **缺点** | |----------------------------------|---------------------------------------| | 提高代码复用性(公共逻辑集中管理)[^2] | 可能因过度抽象增加系统复杂度 | | 子类只需关注差异部分 | 父类修改模板方法会影响所有子类[^1] | | 符合开闭原则(扩展开放,修改关闭) | 对简单流程可能造成设计过度[^3] | --- #### 六、与其他模式对比 - **策略模式(Strategy Pattern)** 策略模式通过组合替换整个算法,而模板方法模式通过继承修改部分步骤[^4]。 - **工厂方法模式(Factory Method)** 工厂方法是模板方法的一种特殊形式,专注于对象创建步骤的分工。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值