Java设计模式:模板方法模式
? 作者主页:欢迎来到我的技术博客😎
? 个人介绍:大家好,本人热衷于Java后端开发,欢迎来交流学习哦!( ̄▽ ̄)~*
🍊 如果文章对您有帮助,记得关注、点赞、收藏、评论??????
📣 您的支持将是我创作的动力,让我们一起加油进步吧!!!🎉🎉
一、模板方法模式的定义
在软件开发中,某个方法的实现需要多个步骤,其中有些步骤是固定的,而有些步骤并不固定,存在可变性。为了提高代码的复用性和系统的灵活性,可以使用 模板方法模式 的设计模式对这类情况进行设计。
在模板方法模式中将实现功能的每一步骤所对应的方法称为 基本方法,而将调用这些基本方法同时定义基本方法的执行次序的方法称为 模板方法。
定义: 定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
模板方法模式将一些复杂流程的实现步骤封装在一系列基本方法中,在抽象父类中提供一个称为模板方法的方法的定义这些基本方法的执行次序,并且可以通过其子类来覆盖某些步骤,从而使得相同的算法框架可以有不同的执行结果。
二、模板方法模式的结构
模板方法模式包含以下重要角色:
-
抽象类 : 负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
-
模板方法 : 定义了算法的骨架,按某种顺序调用其包含的基本方法。
-
基本方法 : 是实现算法中各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:
- 抽象方法 : 一个抽象方法由抽象类声明、由其具体子类实现。
- 具体方法 : 一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
- 钩子方法 : 在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx
,返回值类型为boolean
类型。
-
-
具体子类 : 实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。
三、模板方法模式的实现
需求案例:去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、离开银行等操作。
我们可以发现,在这个流程中取号、排队和对离开银行的操作对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
现在使用模板方法模式来实现上述案例,类图如下:
?
具体的类设计如下:
抽象类:
public abstract class AbstractClass {
//模板方法(定义基本方法的执行顺序)
public final void service() {
getNum();
doWait();
doService();
doExit();
}
//下面的方法都称为基本方法
//第一步:取号(一样的,所以直接实现)
public void getNum() {
System.out.println("取号");
}
//第二部:排队(一样的,所以直接实现)
public void doWait() {
System.out.println("排队");
}
//第三步:办理具体业务(不一样,由子类实现)
public abstract void doService();
//第四步:离开银行(一样的,所以直接实现)
public void doExit() {
System.out.println("离开银行");
}
}
存款业务类:
public class SaveMoney extends AbstractClass {
public void doService() {
System.out.println("办理存款业务");
}
}
取款业务类:
public class TakeMoney extends AbstractClass {
public void doService() {
System.out.println("办理取款业务");
}
}
客户端类:
public class client {
public static void main(String[] args) {
//办理存款业务
SaveMoney saveMoney = new SaveMoney();
saveMoney.service();
System.out.println("---------------");
//办理取款业务
TakeMoney takeMoney = new TakeMoney();
takeMoney.service();
}
}
测试结果如下:
?
注意:为防止恶意操作,一般模板方法都加上 final 关键词。
四、模板方法模式的扩展
在上述的案例中,在现实生活中像取号和排队操作是不一定需要执行,比如一般在一般中如果是VIP的话那去办理业务一般是有独立的窗口,不需要去取号和排队的,因此,VIP的用户只需执行办理具体业务和离开银行即可。
我们可以引入模板方法模式中的 钩子方法,通过钩子方法返回的布尔值来决定是否需要执行模板方法中的某个操作。
钩子方法 的作用主要体现在以下几个方面:
-
提供默认行为: 钩子方法通常在抽象类中提供了一个默认的实现,这个默认实现可以在模板方法中被调用。如果具体子类选择不覆盖钩子方法,就会使用这个默认行为。这使得在不同的子类中可以有一些通用的行为。
-
允许选择性的行为扩展: 具体子类可以选择性地覆盖钩子方法,以添加或修改某些行为,以适应特定的需求。这为子类提供了一定的灵活性,使其能够自定义模板方法的部分行为,而不需要修改整个模板方法。
-
控制模板方法执行流程: 钩子方法的返回值通常被用于在模板方法中控制流程。例如,可以使用钩子方法返回的布尔值来决定是否执行模板方法中的某个步骤。这样,通过覆盖钩子方法,可以在不同的子类中控制模板方法的执行流程。
接下来,我们使用钩子方法对上述例子进行改造,具体实现类如下:
抽象类:
public abstract class AbstractClass {
//模板方法(定义基本方法的执行顺序)
public final void service() {
if (isVIP()) {
getNum();
doWait();
}
doService();
doExit();
}
//钩子方法
public boolean isVIP() {
return false;
}
//下面的方法都称为基本方法
//第一步:取号(一样的,所以直接实现)
public void getNum() {
System.out.println("取号");
}
//第二部:排队(一样的,所以直接实现)
public void doWait() {
System.out.println("排队");
}
//第三步:办理具体业务(不一样,由子类实现)
public abstract void doService();
//第四步:离开银行(一样的,所以直接实现)
public void doExit() {
System.out.println("离开银行");
}
}
存款业务类:
public class SaveMoney extends AbstractClass {
public void doService() {
System.out.println("办理存款业务");
}
//覆盖钩子方法
@Override
public boolean isVIP() {
return true;
}
}
取款业务类:
public class TakeMoney extends AbstractClass {
public void doService() {
System.out.println("办理取款业务");
}
}
客户端类:
public class client {
public static void main(String[] args) {
//办理存款业务
SaveMoney saveMoney = new SaveMoney();
saveMoney.service();
System.out.println("---------------");
//办理取款业务
TakeMoney takeMoney = new TakeMoney();
takeMoney.service();
}
}
测试结果:
五、模板方法模式的优缺点
优点:
- 提高代码服复用性 : 将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。
- 实现了反向控制 : 通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 ,并符合“开闭原则”。
缺点:
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
六、模板方法模式的使用场景
- 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
- 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。
?
非常感谢您阅读到这里,如果这篇文章对您有帮助,希望能留下您的点赞👍 关注💖 分享👥 留言💬thanks!!!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!