一、什么是模板模式
模板模式是一种基于继承实现的设计模式,它是行为型的模式。
主要思想是将定义的算法抽象成一组步骤,在抽象类种定义算法的骨架,把具体的操作留给子类来实现。
通俗地说,模板模式就是将某一行为制定一个框架,然后子类填充细节。比如说做菜,流程通常就是洗菜、切菜、炒菜等步骤,那么这个流程就可以看作是一个模板,而具体做什么菜由子类来实现。
二、角色组成
抽象类(Abstract):定义了算法骨架,包含一个或多个抽象方法,这些方法由子类来具体实现。抽象类中通常还包含一个模板方法,用来调用抽象方法和具体方法,控制算法的执行顺序;还可以定义钩子方法,用于在算法中进行条件控制。
具体类(Concrete Class):继承抽象类,实现抽象方法。
三、优缺点
优点:
提高代码复用性:将算法的骨架定义在父类中,子类只需要实现具体的细节部分,减少了代码的重复。
符合开闭原则:在模板模式种,由父类控制子类的执行,通过子类对父类进行扩展增加新的行为,符合“开闭原则”。
提高代码可维护性:模板模式定义了一套固定的模板,便于开发人员理解和修改,易于维护。
缺点:
部分子类可能无法灵活定制:由于模板模式制定的是一个固定的结构,所以某些子类可能无法适用,导致无法实现特定的需求或定制。
类的数量增加:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
四、应用场景
4.1 生活场景
写文章:假设我们要写一篇文章,其中包括标题、引言、正文和结论等部分。我们可以将文章的写作过程定义为一个模板,在抽象类中定义写作方法,如先写标题、再写引言、接着写正文,最后写结论。具体类就是每篇文章的实现类,它们可以根据主题和内容的不同来实现对应的部分。这样,每个具体文章类只需要关注自己特定的内容,而写作的步骤则由模板来控制
学习流程:比如学习某门课程的流程,在学习过程中有一些共同的步骤,比如预习、上课、复习做练习等。我们可以定义一个抽象类StudyCourse,在其中定义学习的方法。具体类就是每门具体课程的实现类,它们根据课程内容和学习方式来实现抽象类中的方法。模板方法则是定义在抽象类中的一组方法,用于规定学习的整体流程和一些基本规则。
4.2 java场景
JdbcTemplate:JdbcTemplate提供了一系列的模板方法,如execute、query、update等。开发者可以通过继承JdbcTemplate并实现相应的抽象方法来完成数据库操作的具体实现。
HttpServlet:HttpServlet类是一个抽象类,提供了handleRequest、doGet、doPost等模板方法,用于处理HTTP请求。Servlet开发者可以继承HttpServlet并实现这些方法来处理具体的请求,从而完成一个特定的Servlet实现。
Servlet过滤器:Java Servlet
API中提供了过滤器(Filter)接口,用于对Servlet请求进行拦截和处理。该接口中定义了一个doFilter()方法,该方法是一个模板方法,由子类实现具体的请求拦截和处理方式。
五、代码实现
下面以订外卖为例,解释一下模板模式。假设订外卖的过程包含三个步骤:选择外卖、支付、取外卖、是否打赏,我们可以定义一个OderFood的抽象类,那么选择外卖就可以是抽象方法,需要子类取实现它,支付和取外卖可以定义为具体方法,另外是否打赏为钩子方法,子类可以决定是否对算法的不同进行挂钩,还需要定义一个模板方法,用以控制流程;不同的商家,如KFC、星巴克就是具体类。
5.0 UML类图
5.1 OrderFood——抽象类(Abstract)
/**
*
* 1.抽象类(Abstract Class):点外卖
* 包含选择外卖、支付、取外卖三个方法,
* 其中选择外卖为抽象方法,需要子类实现
* 支付、取外卖为具体方法
* 另外是否打赏为钩子方法,子类可以决定是否对算法的不同进行挂钩
*/
public abstract class OrderFood {
//模板方法
public void order(){
selectFood();
pay();
getFood();
}
//选择外卖 抽象方法 由子类实现具体细节
public abstract void selectFood();
//是否打赏 钩子方法 可以重写来做条件控制
public boolean isGiveAward(){
return false;
}
//-------具体方法----------
public void pay(){
System.out.println("支付成功,外卖小哥正在快马加鞭~~");
}
//取外卖
public void getFood(){
System.out.println("取到外卖");
if (isGiveAward()){
System.out.println("打赏外卖小哥");
}
}
}
5.2 具体类(Concrete Class)
/**
*
* 具体类(Concrete Class):星巴克
*/
public class Starbucks extends OrderFood{
//实现父类方法
@Override
public void selectFood() {
System.out.println("一杯抹茶拿铁");
}
//重写钩子方法,打赏外卖小哥
@Override
public boolean isGiveAward(){
return true;
}
}
/**
*
* 具体类(Concrete Class):KFC
*/
public class KFC extends OrderFood{
@Override
public void selectFood() {
System.out.println("一份汉堡炸鸡四件套");
}
}
5.3 testTemplate
/**
*
* 模板模式测试类
*/
@SpringBootTest
public class TestTemplate {
@Test
void testTemplate(){
//星巴克(重写了钩子方法,打赏外卖小哥)
OrderFood orderFood=new Starbucks();
orderFood.order();
System.out.println("--------KFC------------");
//KFC
OrderFood orderFood1=new KFC();
orderFood1.order();
}
}
六、总结
我们可以把模板看作是一个公共的蓝本,而子类就像是根据这个蓝本来实现自己独特的需求。所以当我们在开发中遇到一些情况,比如多个类共享一些相同的操作,或者说想要控制子类扩展某个算法的一部分功能时,就可以考虑模板模式了。另外,还有以下几个适用场景以做参考:
子类需要扩展算法的部分功能:当需要控制子类对算法的某些步骤进行扩展或修改,同时保持算法骨架不变时,可以使用模板模式。
实现多个算法的不同细节:当需要在不同的场景下使用相同的算法,但是细节实现不同,可以使用模板模式。
需要统一流程的业务:在一些对流程敏感的业务中,例如订餐、下单等,使用模板方法模式可以统一流程,使代码更加简洁和易维护。并且在统一流程的同时,也能保证业务的正确性。
注意事项: 为防止恶意操作,一般模板方法都加上 final 关键词。