1、模板设计模式
1、模板设计模式介绍
1.1、什么是模板模式
模板模式是一种基于继承实现的设计模式,它是行为型的模式。
主要思想是将定义的算法抽象成一组步骤,在抽象类种定义算法的骨架,把具体的操作留给子类来实现。
通俗地说,模板模式就是将某一行为制定一个框架,然后子类填充细节。比如说做菜,流程通常就是洗菜、切菜、炒菜等步骤,那么这个流程就可以看作是一个模板,而具体做什么菜由子类来实现。
最重要一点:抽象方法中定义调用过程,定义业务过程,然后子类实现具体的业务方法。
1.2、主要解决问题
解决在多个子类中重复实现相同的方法的问题,通过将通用方法抽象到父类中来避免代码重复,子类实现属于自己的方法。
在父类中做具体的调用过程规划,子类实现相应的具体方法,可以抽象类继承抽象类,具体类继承抽象类,每一层都实现属于自己的方法,一个类不应该实现太多的功能,应该将不同的方法分散到不同的子类中去实现。
1.4、实现方式
- 定义抽象父类:包含模板方法和一些抽象方法或具体方法。
- 实现子类:继承抽象父类并实现抽象方法,不改变算法结构。
1.5、关键代码
- 模板方法:在抽象父类中定义,调用抽象方法和具体方法。
- 抽象方法:由子类实现,代表算法的可变部分。
- 具体方法:在抽象父类中实现,代表算法的不变部分。
1.6、使用建议
- 当有多个子类共有的方法且逻辑相同时,考虑使用模板方法模式。
- 对于重要或复杂的方法,可以考虑作为模板方法定义在父类中。
2、角色组成
- 抽象类(Abstract):定义了算法骨架,包含一个或多个抽象方法,这些方法由子类来具体实现。抽象类中通常还包含一个模板方法,用来调用抽象方法和具体方法,控制算法的执行顺序;还可以定义钩子方法,用于在算法中进行条件控制。
- 一般情况下模板方法被设计为final方法,不允许子类继承和重写。
- 具体类(Concrete Class):继承抽象类,实现具体的抽象方法,父类在模板方法中做调用。
- 模板方法,模板方法一般声明为final,防止恶意重写,修改调用逻辑。
3、优缺点
优点:
- 提高代码复用性:将算法的骨架定义在父类中,子类只需要实现具体的细节部分,减少了代码的重复提取公共代码:减少代码重复,便于维护。
- 符合开闭原则:在模板模式种,由父类控制子类的执行,通过子类对父类进行扩展增加新的行为,符合“开闭原则”。
- 提高代码可维护性:模板模式定义了一套固定的模板,便于开发人员理解和修改,易于维护。
- 封装不变部分:算法的不变部分被封装在父类中。
- 扩展可变部分:子类可以扩展或修改算法的可变部分。
缺点:
部分子类可能无法灵活定制:由于模板模式制定的是一个固定的结构,所以某些子类可能无法适用,导致无法实现特定的需求或定制。
类的数量增加:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
类数目增加:每个不同的实现都需要一个子类,可能导致系统庞大。
多个抽象类和具体类相互继承,可读性差,调用关系复杂。
4、应用场景
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()方法,该方法是一个模板方法,由子类实现具体的请求拦截和处理方式。
5、代码实现
下面以订外卖为例,解释一下模板模式。
假设订外卖的过程包含三个步骤:选择外卖、支付、取外卖、是否打赏,我们可以定义一个OderFood的抽象类,那么选择外卖就可以是抽象方法,需要子类取实现它,支付和取外卖可以定义为具体方法,另外是否打赏为钩子方法,子类可以决定是否对算法的不同进行挂钩,还需要定义一个模板方法,用以控制流程;不同的商家,如KFC、星巴克就是具体类。
5.1、UML类图
5.2、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.3、具体类(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.4、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();
}
}
6、总结
我们可以把模板看作是一个公共的蓝本,而子类就像是根据这个蓝本来实现自己独特的需求。所以当我们在开发中遇到一些情况,比如多个类共享一些相同的操作,或者说想要控制子类扩展某个算法的一部分功能时,就可以考虑模板模式了。另外,还有以下几个适用场景以做参考:
- 子类需要扩展算法的部分功能:当需要控制子类对算法的某些步骤进行扩展或修改,同时保持算法骨架不变时,可以使用模板模式。
- 实现多个算法的不同细节:当需要在不同的场景下使用相同的算法,但是细节实现不同,可以使用模板模式。
- 需要统一流程的业务:在一些对流程敏感的业务中,例如订餐、下单等,使用模板方法模式可以统一流程,使代码更加简洁和易维护。并且在统一流程的同时,也能保证业务的正确性。
注意事项: 为防止恶意操作,一般模板方法都加上 final 关键词。