1模板模式
1.1简介
备注:文章标题暂时没分类,
模板设计模式,使用了java的继承机制,定义如下:定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤
类图如下
AbstractClass叫做抽象模板,是模板基类,该类的方法分为两类
①基本方法:是子类实现的方法,在模板方法中调用,基本方法尽量设计为protected类型,符合迪米特法则,不需要暴露的属性或方法尽量不要设置为protected类型。实现类若非必要,尽量不要扩大父类中的访问权限
②模板方法:可以有一个或几个,一般是一个具体的方法,也就是一个模型框架,实现对基本方法的调度,完成固定的逻辑,一般模板方法都加上final关键字,不允许被覆写。
两个实现类ConcreteClass1和ConcreteClass2属于具体模板,实现了父类所定义的一个或多个的抽象基本方法,
1.2样例代码
抽象模板基类
package com.nie.template;
public abstract class AbstractClass {
//基本方法
protected abstract void doSomething();
//基本方法
protected abstract void doAnything();
//模板方法
public void templateMethod(){
//调用基本方法,完成相关的逻辑
this.doSomething();
this.doAnything();
}
}
具体模板实现
package com.nie.template;
public class Concrete1Class extends AbstractClass{
//实现基本方法
@Override
protected void doSomething() {
//具体业务逻辑处理
}
@Override
protected void doAnything() {
//具体业务逻辑处理
}
}
package com.nie.template;
public class Concrete2Class extends AbstractClass{
//实现基本方法
@Override
protected void doSomething() {
//具体业务逻辑处理
}
@Override
protected void doAnything() {
//具体业务逻辑处理
}
}
2悍马车辆模型V1版本
业务场景:制造悍马车模(汽车模型),v1版本 只做基本的实现,不考虑扩展
悍马模型,定义一个抽象基类,用不同型号的模型实现类,每个模型所具有的五个方法,启动车辆,停止车辆,鸣叫喇叭,发动引擎,汽车跑起来
抽象悍马模型基类
package com.nie.template.version1;
/**
* @description: TODO
* @author: nzy
* @date: 2021/11/17 13:52
* @version: v1.0
* 业务场景:制造悍马车模(汽车模型)
* v1版本 只做基本的实现
* 悍马车模型
* 悍马模型,定义一个抽象基类,用不同型号的模型实现类,
* 每个模型所具有的五个方法,启动车辆,停止车辆,鸣叫喇叭,发动引擎,汽车跑起来
*/
public abstract class HummerModel {
public abstract void start();//启动车辆
public abstract void stop();//停止车辆
public abstract void alarm();//鸣叫喇叭
public abstract void engineBoom();//发动引擎
public abstract void run();//汽车跑起来
}
H1型号悍马模型
package com.nie.template.version1;
/**
* @description: TODO
* @author: nzy
* @date: 2021/11/17 13:58
* @version: v1.0
* H1型号的悍马车 继承悍马基类模板
*/
public class HummerH1Model extends HummerModel{
@Override
public void start() {
System.out.println("悍马H1启动");
}
@Override
public void stop() {
System.out.println("悍马H1停车");
}
@Override
public void alarm() {
System.out.println("悍马H1鸣笛");
}
@Override
public void engineBoom() {
System.out.println("悍马H1引擎发动声音");
}
@Override
//汇总方法,方便用户测试
public void run() {
//1启动汽车
this.start();
//2引擎启动
this.engineBoom();
//3遇到行人,按喇叭
this.alarm();
//4到达目的停车
this.stop();
}
}
H2型号悍马模型
package com.nie.template.version1;
/**
* @description: TODO
* @author: nzy
* @date: 2021/11/17 13:58
* @version: v1.0
* H2型号的悍马车 继承悍马基类模板
*/
public class HummerH2Model extends HummerModel{
@Override
public void start() {
System.out.println("悍马H2启动");
}
@Override
public void stop() {
System.out.println("悍马H2停车");
}
@Override
public void alarm() {
System.out.println("悍马H2鸣笛");
}
@Override
public void engineBoom() {
System.out.println("悍马H2引擎发动声音");
}
@Override
//汇总方法,方便用户测试
public void run() {
//1启动汽车
this.start();
//2引擎启动
this.engineBoom();
//3遇到行人,按喇叭
this.alarm();
//4到达目的停车
this.stop();
}
}
用户测试类
public class HummerModelTest {
public static void main(String[] args) {
//演示模型
HummerH1Model h1Model = new HummerH1Model();
h1Model.run();
HummerH2Model h2Model = new HummerH2Model();
h2Model.run();
}
}
运行结果为
3悍马车辆模型V2版本
v1版本存在的问题,两个实现类的run()方法的逻辑完全相同,那么就要考虑run()方法应该抽取到抽象类中,而不是存在实现类上,抽象所有子类的共性封装。
在软件开发过程中,如果相同的一段代码拷贝过两次,就需要对设计产生怀疑,v2版本做如下修改,将run()方法提取到抽象基类中,并把普通基本方法设置为protected,模板方法加上final。
类图如下
package com.nie.template.version2;
/**
* 业务场景:制造悍马车模(汽车模型)
* v2版本 发现v1版本两个实现类run方法执行逻辑都是相似的
* 悍马车模型
* 悍马模型,定义一个抽象基类,用不同型号的模型实现类,
* 每个模型所具有的五个方法,启动车辆,停止车辆,鸣叫喇叭,发动引擎,汽车跑起来
*/
public abstract class HummerModel {
protected abstract void start();//启动车辆
protected abstract void stop();//停止车辆
protected abstract void alarm();//鸣叫喇叭
protected abstract void engineBoom();//发动引擎
//汽车跑起来
//更改抽象方法为普通方法,定义了车模跑起来的运行规则,两个实现类的方法可以删除
public final void run(){
//1启动汽车
this.start();
//2引擎启动
this.engineBoom();
//3遇到行人,按喇叭
this.alarm();
//4到达目的停车
this.stop();
};
}
两个实现类只需删除run()方法即可,测试效果跟v1版本一样,证明修改成功
4模板方法分析
优点:
①封装不变部分,扩展可变部分
把不变的部分的算法封装到父类实现,而可变部分的则通过继承类来扩展,在悍马模型列子中,就可以扩展其他型号的悍马模型,比如增加H3、H4、Hn等型号的悍马模型,增加一个子类,实现父类的基本方法就可以。
②提取公共部分代码,便于维护
v1版本的run()方法就走了一些弯路,v2版本把子类的相同逻辑封装到抽象父类中,维护人员可以清晰的找到代码逻辑关系
③行为由父类控制,子类实现
基本方法是由子类实现的,子类可以通过扩展的方式增加相应的功能,符合开闭原则。
缺点:按照之前的设计习惯,抽象类负责声明最抽象,最一般的事物属性和方法,实现类完成具体的事物属性和方法,而模板设计模式却颠倒了,抽象类定义了部分抽象方法,由子类实现,子类执行的结果影响了父类的结果。在复杂的项目中,会带来代码阅读的难度,会让新手产生不适应的感觉。
使用场景:
多个子类有公有的方法,并且逻辑基本相同时
重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能由各个子类实现
重构项目时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子方法(下一个标题)来约束其行为
5钩子方法
v2版本看起来还挺好,但是有一个问题,有的车模不想让喇叭一直响,可以人为的去控制,这个时候就要用钩子方法了,
业务场景:H1悍马模型不响喇叭,H2悍马模型响喇叭
先上类图
该版本在抽象类HummerModel中增加了一个普通方法isAlarm,来判断各个型号的悍马是否需要声音,由实现类是否考虑覆写该方法(默认是响声音)
抽象模板类
package com.nie.template.version3;
/**
* 业务场景:制造悍马车模(汽车模型)
* v2版本 发现v1版本两个实现类run方法执行逻辑都是相似的
* 悍马车模型
* 悍马模型,定义一个抽象基类,用不同型号的模型实现类,
* 每个模型所具有的五个方法,启动车辆,停止车辆,鸣叫喇叭,发动引擎,汽车跑起来
*/
public abstract class HummerModel {
protected abstract void start();//启动车辆
protected abstract void stop();//停止车辆
protected abstract void alarm();//鸣叫喇叭
protected abstract void engineBoom();//发动引擎
//汽车跑起来
//更改抽象方法为普通方法,定义了车模跑起来的运行规则,两个实现类的方法可以删除
public final void run(){
//1启动汽车
this.start();
//2引擎启动
this.engineBoom();
//3遇到行人,按喇叭
if (isAlarm()){
this.alarm();
}
//4到达目的停车
this.stop();
};
//钩子方法,是否要具有响喇叭功能 默认是开启的
protected boolean isAlarm(){
return true;
}
}
H1悍马模型不响喇叭
package com.nie.template.version3;
/**
* @description: TODO
* @author: nzy
* @date: 2021/11/17 13:58
* @version: v1.0
* H1型号的悍马车 继承悍马基类模板
*/
public class HummerH1Model extends HummerModel {
private boolean isAlarm = false;//默认不需要喇叭
@Override
protected void start() {
System.out.println("悍马H1启动");
}
@Override
protected void stop() {
System.out.println("悍马H1停车");
}
@Override
protected void alarm() {
System.out.println("悍马H1鸣笛");
}
@Override
protected void engineBoom() {
System.out.println("悍马H1引擎发动声音");
}
@Override
protected boolean isAlarm() {
return isAlarm;//不响喇叭
}
public void setAlarmFlag(boolean isAlarm) {
this.isAlarm = isAlarm;
}
}
H2悍马模型响喇叭
package com.nie.template.version3;
/**
* @description: TODO
* @author: nzy
* @date: 2021/11/17 13:58
* @version: v1.0
* H2型号的悍马车 继承悍马基类模板
*/
public class HummerH2Model extends HummerModel {
@Override
protected void start() {
System.out.println("悍马H2启动");
}
@Override
protected void stop() {
System.out.println("悍马H2停车");
}
@Override
protected void alarm() {
System.out.println("悍马H2鸣笛");
}
@Override
protected void engineBoom() {
System.out.println("悍马H2引擎发动声音");
}
}
这版的代码还是比较完美的,各个型号的悍马可以自己控制是否响喇叭,当然其他的普通方法的是否执行也是同样的逻辑,isAlarm()就是钩子方法,由子类的一个方法返回值决定公共部分的执行结果
总结:
在以前的开发的思想中,父类是不可以调用子类的方法。从今天案例来看是可以通过子类方法的返回值来修改父类业务代码的执行逻辑,变向的调用了子类的方法。
常见的父类调用子类的方法为
①把子类传递到父类的有参构造中,然后调用
②使用反射的方式调用,反射是啥都可以调用,哈哈
③父类调用子类的静态方法
这三种方式 在开发中都是不被允许的。因为思想上是有冲突的,如果要调用子类,为何还要子类继承父类。
这种情况换个角度理解,父类建立框架,子类在重写了父类的部分方法后,在调用父类继承的方法,产生不同的结果,这正是模板方法的思想,变相的调用了子类的方法。因为修改了子类,影响了父类的执行结果。很多开源框架spring、Mybatis都有用到该思想