行为模式的包,然后创建一个templatemethod模板方法这样的一个包,这里面我们引入一个业务场景,例如说
制作课程,这么一个业务场景,我现在在制作这个视频课程,那对于后端的JAVA课程,还有设计模式这个课程,
在制作的时候,要制作PPT,制作视频,还要决定去写一个手记,还要提供前端的一些素材,例如前端的很多课程呢,
都是要提供前端等素材的,而我们这个设计模式,又不提供图片素材,那不同的课程,制作的步骤主线上是一样的,
但是在细分的一些细节上,还是有所不同,我们这个设计模式,一些图片的素材,我们是不需要提供的,而且本身我们
也没有,那么是否去写手记,这个呢还不一定,有的课程制作的时候必须有配套的手记,那有一些并不是必须的,写手记
并不是非必选项,完全留给子类来扩展,那么我们把这个方法做成钩子方法,那具体怎么实现呢,现在就让我们一起来coding,
很简单,这个设计模式非常非常简单,首先我们创建一个类,这个类是什么呢,抽象类ACourse
这个设计模式学起来也是比较轻松的,就是一个单纯的抽象类,然后下边实现了抽象方法,
Test我们先不看,我们主要看上边,右侧有一个needWriteArticle,这个方法正是重写父类的needWriteArticle,
其他的没有变化,那我们再看一下前端课程这个类,我们要再演进一版,什么业务场景呢,FECourse,这个呢是一个前端
课程,它是一个泛指,例如说,React,Vue,这些都是FECourse,对于后端课程来说,这个是一个具体的设计模式课程,当然我们
没有起后端课程这么一个类,所以我们现在聚焦在FECourse,Vue需要写手记,而React不需要写手记,但是我们的这个类声明的
是FECourse,那如果我们这么写,我们看一下
不要关注Test,还是看上边,模板方法的UML,清晰明了,非常容易理解,所以呢总结来说,对于我们这个模板
方法设计模式,这个ACourse,也就是这个抽象类,就是一个模板,定义了一套标准,他呢是一种行为模式,把这些makePPT,
makeVideo,这种固定的写法,这些步骤,以及这些顺序呢,都固定好,对于需要钩子方法的这些选择性方法,开放出去,
而子类对于writeArticle实现是没有疑义的,而对于packageCourse,完全交给子类来实现,这里面要注意一下这个final,
一定要加final,否则子类是可以重写makeCourse,也就是把步骤全部打乱,都OK的,模板方法就是为了定义一个模板,
不想其他类把流程步骤给打乱,那模板方法这个模式的coding呢,就到这里,相信你们,设计模式肯定能学会,而且肯定能
理解好
package com.learn.design.pattern.behavioral.templatemethod;
/**
* 它是一个抽象类
*
*
* @author Leon.Sun
*
*/
public abstract class ACourse {
/**
* 首先这里要写一个protected的方法
* 这个权限控制
* 这个方法声明成final的
* 因为在ACourse里面定义的模板
* 是不想被修改的
* 所以这个方法声明成final的
* 我们都用void makeCourse
* 制作课程
* 这里面注意
* 声明final是不希望子类可以覆盖这个方法
* 防止修改我们这个制作课程流程的执行顺序
* 那这个方法先放到这儿
* 现在我们来拆解一下这个小步骤
* 首先我们要做一个PPT
* 没什么说的
* 所有的课程都要制作PPT
* 无论现在网络上是免费的课程
* 还是收费的课程
*
* 这里面要怎么写呢
* 首先我们这个类是为了定义一个模板
* 那这个模板关键方法是makeCourse
* 首先我们调用一下makePPT
* 必须要制作PPT
* 然后要makeVideo
* 就是必须要makeVideo
* 那对于是否写手记来配套这个课程
* 是可选的
* 我们交由钩子方法来实现
* 比如这里有一个needWriteArticle方法
* 如果这个方法返回true的话
* 我们调用writeArticle
* 那这里就使用了模板方法里面的钩子方法
* 最后我们再调用一下packageCourse
* 那这里面刚刚也说了
* 对于makePPT makeVideo writeArticle这里面的实现
* 对于所有子类是没有疑义的
* 所以我们声明为final的
*
*
*
*/
protected final void makeCourse(){
this.makePPT();
this.makeVideo();
if(needWriteArticle()){
this.writeArticle();
}
this.packageCourse();
}
/**
* 直接制作PPT
* 你们想一下void makePPT
* 这个方法是不是也要是final的呢
* 如果这个方法所有的子类都不需要重写的话
* 那我们就要把它声明成final的
* 也就是说这个行为是固定不变的
* 制作什么课程
* 都要制作PPT
* 并且这里面的实现
* 是满足所有子类的
*
*
*/
final void makePPT(){
System.out.println("制作PPT");
}
/**
* 同理制作视频
* 那我们这里的制作都用make这个动词了
* 那我们再想象一下
*
*
*/
final void makeVideo(){
System.out.println("制作视频");
}
/**
* 编写手记
* 编写手记他是固定的
* 就是编写手记
* 玩不出来什么花样
* 所以对于编写手记这个实现
* 所有的子类是认可的
* 但是小明要编写手记
* 小刚就不编写手记了
* 也就是说对于这个课程来说
* 编写手记是一个可选项
* 但是对于这里的实现
* 子类都是认可的
* 所以写手记这个方法
* 我们也把他声明成final的
* 所以我们声明一个钩子方法
*
*
*/
final void writeArticle(){
System.out.println("编写手记");
}
//钩子方法
/**
* 返回值是布尔
* 是否需要编写手记
* 默认return false
* 并且他不是final的
* 子类可以覆盖他
* 写完钩子方法之后我们要写一个钩子方法
*
*
* @return
*/
protected boolean needWriteArticle(){
return false;
}
/**
* 包装课程
* 那对于设计模式这个包装来说呢
* 我们要把项目的源码
* 放到这个素材里面
* 而对于一些前端的课程呢
* 也要放源码
* 他们还要放一些图片的素材
* 也就是对于不同的课程
* 包装课程
* 上线之前
* 提供的素材呢
* 都是不一样的
* 而把这个方法
* 完全交给子类来实现
* 所以对于使用模板方法这个设计模式的时候
* 我们对于业务的一个模型
* 一定要抽象化
* 对于扩展性以及业务后续的发展
* 哪些行为是固定的
* 哪些行为是要交给子类的
* 哪些行为是可选的
* 这些都要把这个业务分析透
* 然后再来设计这个模板方法
* 设计模式的使用场景
* 那现在我们来声明具体的子类实现课程
* 例如DesignPatternCourse
*
*
*/
abstract void packageCourse();
}
package com.learn.design.pattern.behavioral.templatemethod;
/**
* 我们让他继承ACourse
* 实现里面的具体方法
* 现在抽象方法只有一个
*
*
* @author Leon.Sun
*
*/
public class DesignPatternCourse extends ACourse {
/**
* 打包我们写一下具体的实现
* 提供课程的JAVA源代码
* 因为现在设计的业务场景
* 已经覆盖了模板方法的业务场景
* 所以对于设计模式这个课程来说
* 在包装课程的时候
* 我们需要提供JAVA源代码
* 我们再写一个SECourse
* 前端课程
*
* 可以来到这里试一下
* 我们发现makePPT makeVideo makeArticle并不能重写
* 这正是final的一个作用
* 那我们现在回到测试函数里边
*
*
*/
@Override
void packageCourse() {
System.out.println("提供课程Java源代码");
}
@Override
protected boolean needWriteArticle() {
return true;
}
}
package com.learn.design.pattern.behavioral.templatemethod;
/**
* 让他继承ACourse
* 这里面也很简单
*
*
* @author Leon.Sun
*
*/
public class FECourse extends ACourse {
/**
* 默认是false
*
*
*/
private boolean needWriteArticleFlag = false;
/**
* 首先提供课程的前端代码
* 然后呢还要提供一个
* 提供课程的图片等多媒体素材
* 视频等等之类的
* 视频嵌入到前端页面里
* 使用的多媒体素材
* 那我们现在来看一下
* 他的UML图
*
*
*/
@Override
void packageCourse() {
System.out.println("提供课程的前端代码");
System.out.println("提供课程的图片等多媒体素材");
}
/**
* 现在我们使用构造器的方式
* 那么我们再来到Test里边
*
*
* @param needWriteArticleFlag
*/
public FECourse(boolean needWriteArticleFlag) {
this.needWriteArticleFlag = needWriteArticleFlag;
}
/**
* 代表所有的前端类都要写手记
* 这个又不符合业务扩展的一个方向了
* 那怎么办呢
* 很简单
* 把我们现在的模板方法模式呢
* 再演进一步
* 我们把needWriteArticle
* 再开放给Test应用层
* 怎么开放呢
* 也是非常容易的
*
* 然后我们再重写这个方法
* 返回值是什么呢
* 就是this.needWriteArticleFlag
* 然后我们通过构造器和setter的方式呢
* 把needWriteArticleFlag开放给应用层
*
*
*
*/
@Override
protected boolean needWriteArticle() {
return this.needWriteArticleFlag;
}
}
package com.learn.design.pattern.behavioral.templatemethod;
/**
* 直接run一下
* 看一下结果
* 首先对于后端设计模式开始了
* 制作PPT
* 制作视频
* 提供课程的JAVA源代码
* end
* 前端课程我们看一下
* 制作PPT
* 制作视频
* 提供课程的前端代码
* 提供课程的图片多媒体素材
* 前端课程end
* 那现在我们就要考虑一下
* writeArticle写手记这么一个方法
* 上线是有编写课程手记
* 所以就想重写这个方法
* needWriteArticle
* return什么呢
* return true
*
*
* @author Leon.Sun
*
*/
public class Test {
public static void main(String[] args) {
/**
* 首先我们输出一个后端设计模式课程start
*
*
*/
// System.out.println("后端设计模式课程start---");
/**
* 直接new一个DesignPatternCourse
* 通过父类声明一个引用来指向子类的一个实例
*
* 因为Test里面new的是一个子类对象
* 所以只要我们在这个类里面重写的话
* 那对于模板里面要执行方法的时候
* 拿到的就是子类的这个方法的返回值
* 那对于现在这个设计模式的这个课程
* 他会编写手记
* 因为needWriteArticle默认的是返回false
* 那我们再run一下
* 可以看到后端课程设计的时候
* 有一个编写手记
* 但是前端并没有
* 这个呢和预期一样
* 因为前端并没有编写手记的方法
* 而是选用了模板默认返回值false
* 那这个就是钩子方法的具体使用
* 非常简单
* 我们看一下UML有什么变化呢
*
*
*/
// ACourse designPatternCourse = new DesignPatternCourse();
/**
* 直接调用它的makeCourse()
* 制作课程
*
*
*/
// designPatternCourse.makeCourse();
/**
* 然后写一个end
*
*
*/
// System.out.println("后端设计模式课程end---");
System.out.println("前端课程start---");
/**
* 写一个前端的课程
*
* 现在我们想让我们的前端课程写手记
* 赋值一个true
* 上边我们先注释上
* run一下
* 编写手记了
* 我们再把它改成false
* 再run一下
* 制作课程下边并没有编写手记
* 那这里面想说明什么呢
* 我特意设计这个case
* 就想说对于模板方法
* 抽象的层次
* 例如说
* 设计模式这个课程
* 那在我们这个业务场景中
* 我们把它定义为后端的一个课程
* 其中的一个子课程
* 而FECourse是一个大范围的课程
* 例如说React
* JavaScript
* 或者呢
* Vue这些
* 都是前端课程
* 如果我们没业务类型转化不够合理的话
* 就会发生刚刚我写的这个case
* 他两可以理解在我们这个业务场景中
* 并不是同一个level的
* 也就是设计模式的上一级
* 所以这种情况
* 我们有可能把适当的权限开放给应用层
* 所以刚刚通过这种方式
* 声明一个自己的变量
* 然后通过构造器或者setter的方式呢
* 把这个flag开放给应用层
* 那这种使用方式
* 还是要看具体的应用场景
* 现在的这种写法是为了满足不同的FECourse
* 对于手记可能有不同需求的
* 实现满足方案
* 那我们再看一下UML
*
*
*
*/
ACourse feCourse = new FECourse(false);
feCourse.makeCourse();
/**
* 前端课程end
*
*
*/
System.out.println("前端课程end---");
}
}