模版模式是我最喜欢的一个设计模式,也是最早尝试使用过的一个设计模式。根据《大话设计模式》中的描述:

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模版方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

很关键的一点是算法,或者说函数的整体流程是不变的,但是部分流程是可变的、可扩展的,交由子类去实现。

举一个简单的例子,比如考试,在系统中的流程是:

系统创建考试 -> 上传试题(可选) -> 系统发布考试 -> 获取考生成绩 -> 生成考试报表(可选)

定义一个考试抽象类:

/**
* @author Dongguabai
* @Description
* @Date 创建于 2020-05-04 09:31
*/
public abstract class AbstractExam {

protected String examName;

protected AbstractExam(String examName) {
this.examName = examName;
}

/**
* 考试流程
*/
final void makeExam() {
createExam(examName);
if (needUploadQuestions()) {
uploadQuestions();
}
publishExam();
List<Object> examDatas = getExamData();
if (needGenerateReport()){
generateReport(examDatas);
}
}

/**
* 创建考试
*/
final void createExam(String examName) {
System.out.printf("系统创建考试:【%s】", examName);
System.out.println();
}

void publishExam() {
System.out.println("系统发布考试");
}

/**
* 上传试题
*/
final void uploadQuestions() {
System.out.println("系统上传试题");
}

/**
* 上传试题钩子
*
* @return
*/
protected boolean needUploadQuestions() {
return true;
}

/**
* 获取考试数据
*/
protected abstract List<Object> getExamData();

/**
* 生成数据报表
*/
protected void generateReport(List<Object> examDatas) {
System.out.println("默认数据报表:"+examDatas);
}

/**
* 数据报表钩子
*
* @return
*/
protected boolean needGenerateReport() {
return false;
}
}

考试又会分为线上考试和线下考试,这里设置了两个钩子,就是有的考试需要上传试题,比如线上考试,但是线下考试不需要上传试题;再比如都是线上考试,但是有的考试需要数据报表,有的考试不需要数据报表。

线上考试类:

package com.example.simplespringboot.dongguabai.template;

import java.util.ArrayList;
import java.util.List;

/**
* @author Dongguabai
* @Description 线上考试
* @Date 创建于 2020-05-04 09:48
*/
public class OnlineExam extends AbstractExam {

private boolean needGenerateReport;

protected OnlineExam(String examName) {
super(examName);
}

public OnlineExam(String examName, boolean needGenerateReport) {
super(examName);
this.needGenerateReport = needGenerateReport;
}

@Override
protected List<Object> getExamData() {
List<Object> examDatas = new ArrayList<>();
examDatas.add(this.examName + "-成绩1");
examDatas.add(this.examName + "-成绩2");
return examDatas;
}

@Override
protected boolean needGenerateReport() {
return needGenerateReport;
}

@Override
protected void generateReport(List<Object> examDatas) {
System.out.println("新的数据报表:" + examDatas);
}
}

线下考试:

package com.example.simplespringboot.dongguabai.template;

import java.util.List;

/**
* @author Dongguabai
* @Description 线下考试
* @Date 创建于 2020-05-04 09:56
*/
public class OfflineExam extends AbstractExam {
public OfflineExam(String examName) {
super(examName);
}

@Override
protected List<Object> getExamData() {
return null;
}

@Override
protected boolean needUploadQuestions() {
return false;
}
}

测试一下:

public class Test {
public static void main(String[] args) {
AbstractExam onlineExam = new OnlineExam("线上考试1");
AbstractExam onlineExam2 = new OnlineExam("线上考试2",true);
onlineExam.makeExam();
System.out.println("===========");
onlineExam2.makeExam();
System.out.println("===========");
AbstractExam offlineExxam = new OfflineExam("线下考试");
offlineExxam.makeExam();
}
}

输出:

系统创建考试:【线上考试1】
系统上传试题
系统发布考试
===========
系统创建考试:【线上考试2】
系统上传试题
系统发布考试
新的数据报表:[线上考试2-成绩1, 线上考试2-成绩2]
===========
系统创建考试:【线下考试】
系统发布考试

模版模式无论是在 JDK 源码(如 ​​ClassLoader​​)在很多开源框架中都有用到。比如 Dubbo 的负载均衡就用到了:

设计模式之模版模式_上传

也可以参照 Dubbo,自己写一个,定义一个顶层接口,然后使用一个抽象类去继承,通过模版方法做一些前置操作:

package dgb.nospring.myrpc.registry.loadbalance;

import java.util.List;

/**
* 负载顶层接口
* @author Dongguabai
* @date 2018/11/2 10:11
*/
public interface LoadBalance {

String selectHost(List<String> repos);
}
package dgb.nospring.myrpc.registry.loadbalance;

import org.apache.commons.collections.CollectionUtils;

import java.util.List;

/**
* @author Dongguabai
* @date 2018/11/2 10:15
*/
public abstract class AbstractLoadBanance implements LoadBalance{

/**
* 通过模板方法,做一些前置操作
* @param repos
* @return
*/
@Override
public String selectHost(List<String> repos) {
if(CollectionUtils.isEmpty(repos)){
return null;
}
if(repos.size()==1){
return repos.get(0);
}
return doSelect(repos);
}

/**
* 实现具体的实现负载算法
* @param repos
* @return
*/
protected abstract String doSelect(List<String> repos);

}
package dgb.nospring.myrpc.registry.loadbalance;

import java.util.List;
import java.util.Random;

/**
* 随机负载算法
* @author Dongguabai
* @date 2018/11/2 10:17
*/
public class RandomLoadBanalce extends AbstractLoadBanance{

@Override
protected String doSelect(List<String> repos) {
return repos.get(new Random().nextInt(repos.size()));
}
}

References

  • 《大话设计模式》

欢迎关注公众号

​​​​​

设计模式之模版模式_List_02