设计模式十七--模板方法模式

298 阅读5分钟

这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战

设计模式

WangScaler: 一个用心创作的作者。

声明:才疏学浅,如有错误,恳请指正。

模板方法模式

何为模板模式?举个日常生活中的例子,比如有三个人去银行办理业务,A想存钱,B想取钱,C想转账。然而他们想完成他们的业务之前,都得取号、排队,而办理完业务之后,都得进行评价。

这三个人取号、排队、评价的过程是一样的,那么我们就可以抽取出来,让父类实现,至于业务上这三个人各不相同,只能在子类去实现。

所以模板方法模式就是定义一个操作中的算法骨架,而将算法的一些步骤(办业务)延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。

要写一个模板模式,首先要将不变的部分(取号、排队、评价)找到,在父类中实现。因为我们的取号、排队、评价过程是不变的无需子类去重写,所以我们可以将其定义成final。

Bank.java

package com.wangscaler.template;
​
/**
 * @author WangScaler
 * @date 2021/7/12 19:52
 */public abstract class Bank {
    final void getNumber() {
        System.out.println("取号");
    }
​
    final void lineUp() {
        System.out.println("排队");
    }
​
    abstract void business();
​
    final void evaluate() {
        System.out.println("评价");
    }
}

将可变的部分抽象供子类实现。存钱的业务

SaveMoney.java

package com.wangscaler.template;
​
/**
 * @author WangScaler
 * @date 2021/7/27 13:54
 */public class SaveMoney extends Bank {
    @Override
    void business() {
        System.out.println("存钱");
    }
}

取钱的业务

package com.wangscaler.template;
​
/**
 * @author WangScaler
 * @date 2021/7/27 13:53
 */public class WithdrawMoney extends Bank {
​
    @Override
    void business() {
        System.out.println("取钱");
    }
}

main

package com.wangscaler.template;
​
/**
 * @author WangScaler
 * @date 2021/7/27 13:55
 */public class Tempalte {
    public static void main(String[] args) {
        Bank saveMoneyPeople = new SaveMoney();
        saveMoneyPeople.businessForBank();
        Bank withdrawMoneyPeople = new WithdrawMoney();
        withdrawMoneyPeople.businessForBank();
    }
}

这就是典型的模板模式。

钩子方法

在父类中,定义一个空方法,默认不做任何事,由子类决定要不要重写这个方法,这个方法被称为钩子方法。钩子方法让你的代码更加灵活,将是否需要实现的权限交给用户。

因为我们去银行取钱,当我们取完号排队的过程中,也许是因为前边人太多了,也许是别的事请需要去处理,也许你就是排队进银行吹空调等等,最终没有去办理业务,也就没有去评价。

因为业务上,办理了业务才可以评价,所以我们需要加一个方法用于判断是否实现了业务,如果没有实现则重写判断方法。修改之后的Bank.java

package com.wangscaler.template;
​
/**
 * @author WangScaler
 * @date 2021/7/12 19:52
 */public abstract class Bank {
    final void businessForBank() {
        getNumber();
        lineUp();
        business();
        if (handleBusiness()) {
            evaluate();
        }
    }
​
    final void getNumber() {
        System.out.println("取号");
    }
​
    final void lineUp() {
        System.out.println("排队");
    }
​
    void business(){
        
    }
​
    final void evaluate() {
        System.out.println("评价");
    }
​
    boolean handleBusiness() {
        return true;
    }
}

子类如果不办理业务,则重写判断方法handleBusiness,而business因为是钩子方法,我们不需要实现,则不需要写。

package com.wangscaler.template;
​
/**
 * @author WangScaler
 * @date 2021/7/27 14:27
 */public class Play extends Bank {
    @Override
    boolean handleBusiness() {
        return false;
    }
}

main

package com.wangscaler.template;
​
/**
 * @author WangScaler
 * @date 2021/7/27 13:55
 */public class Tempalte {
    public static void main(String[] args) {
        Bank saveMoneyPeople = new SaveMoney();
        saveMoneyPeople.businessForBank();
        Bank withdrawMoneyPeople = new WithdrawMoney();
        withdrawMoneyPeople.businessForBank();
        Bank playPeople = new Play();
        playPeople.businessForBank();
    }
}

所以playPeople.businessForBank();只会触发取号、排队的动作。

源码中的模板方法

SpringIOC容器的初始化。

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);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }
​
                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }
​
        }
    }
     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) {
    }
      protected void onRefresh() throws BeansException {
    }
}

AbstractApplicationContext中的postProcessBeanFactory和onRefresh都是空实现,即模板方法中的钩子方法,相当于我们例子中的business。

refresh方法相当于我们上述例子中的businessForBank,而refreshBeanFactory、getBeanFactory两个方法就是抽象方法,向我们使用钩子方法之前的business。

那么在子类一定会实现抽象方法,我们去子类看看。

public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
protected final void refreshBeanFactory() throws BeansException {
        if (this.hasBeanFactory()) {
            this.destroyBeans();
            this.closeBeanFactory();
        }
​
        try {
            DefaultListableBeanFactory beanFactory = this.createBeanFactory();
            beanFactory.setSerializationId(this.getId());
            this.customizeBeanFactory(beanFactory);
            this.loadBeanDefinitions(beanFactory);
            this.beanFactory = beanFactory;
        } catch (IOException var2) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var2);
        }
    }
    public final ConfigurableListableBeanFactory getBeanFactory() {
        DefaultListableBeanFactory beanFactory = this.beanFactory;
        if (beanFactory == null) {
            throw new IllegalStateException("BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext");
        } else {
            return beanFactory;
        }
    }

这个子类只实现了抽象方法,钩子方法均没有实现。我们找一找有没有实现钩子方法的子类。

果然,在它的子类的子类AbstractRefreshableWebApplicationContext中找到了。

public abstract class AbstractRefreshableWebApplicationContext extends AbstractRefreshableConfigApplicationContext implements ConfigurableWebApplicationContext, ThemeSource {
    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);
    }
    protected void onRefresh() {
        this.themeSource = UiApplicationContextUtils.initThemeSource(this);
    }
    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);
    }
}

总结

模板模式的几种类型

  • 抽象方法:在抽象类中声明,由具体子类实现。
  • 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
  • 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。

使用场景:

完成某件事情,需要一系列步骤,而这些步骤基本相同,个别不同的步骤实现时可能不同,就可以使用模板方法模式。

缺点:

  • 如果个别步骤的实现方法有很多,那么我们就需要写很多子类去继承。
  • 如果父类增加了新的抽象方法,那么所有子类就都得继承,设计规则避免继承的原因之一。

参考资料