模板方法模式
模板方法模式定义了一个算法的步骤,并允许子类为一个或多个步骤提供具体实现.让子类在不改变算法架构的情况下,重新定义算法中的某些步骤.
定义特定方法(一般为final修饰,防止子类修改)和一般方法,一般方法由子类实现和扩展
优点
- 封装不变部分,扩展可变部分
- 提取公共代码,更好的复用代码,便于维护
- 行为由父类控制,子类实现
缺点
- 每个不同的实现都需要一个子类实现,会导致子类个数增加
适用场景
- 将很多公用或固定的代码封装成模板,少量业务代码由具体子类实现。
- 业务逻辑流程或操作步骤相对固定,具体流程实现根据业务的不同而不同
实战(Excel导入解析)
excel 解析步骤相对固定 可抽象成模板方法,具体操作由子类实现
第一步: 解析Excel 数据
第二步: 校验Excel 标题行校验码
第三步: 数据校验和转换
第四步: 数据入库
这里使用阿里的EasyExcel
- 抽象类
/**
* excel 解析抽象类 模板方法模式
*
* F 代表具体业务类 , T 代表excel字段映射实体类
* @author xup
* @date 2019/11/13 15:17
*/
@Slf4j
public abstract class AbstractExcelParseService<T, F> {
/**
* Excel 解析处理,分四步进行
* 第一步: 解析Excel 数据
* 第二步: 校验Excel 标题行校验码
* 第三步: 数据校验和转换
* 第四步: 数据入库
* @param excelImport excelImport
* @return ExcelParseResult
*/
final ExcelParseResult<T, F> doExcelParse(ExcelImport excelImport){
/*
* 第一步: 解析Excel文件, 返回List
*/
InputStream inputStream;
try {
inputStream = new BufferedInputStream(new FileInputStream(excelImport.getExcelPath()));
} catch (FileNotFoundException e) {
if (log.isErrorEnabled()) {
log.error("文件未找到:" + excelImport.getExcelPath());
}
return ExcelParseResult.result(ExcelParseStatus.EXCEL_NOT_FOUND);
}
List<T> data = parseExcel(inputStream);
if (CollectionUtils.isEmpty(data)){
return ExcelParseResult.result(ExcelParseStatus.EXCEL_NO_DATA);
}
/*
* 第二步: 校验Excel 标题行校验码
*/
ExcelParseStatus excelParseStatus = headCheck(data);
if (excelParseStatus != ExcelParseStatus.DEAL_SUCCESS){
return ExcelParseResult.result(excelParseStatus);
}
/*
* 第三步: 数据校验和转换
*/
ExcelParseResult<T, F> parseResult = dataCheckAndTrans(data, excelImport);
/*
* 第四步: 数据入库
*/
return saveData(parseResult, excelImport);
}
/**
* 第一步: 解析Excel文件, 返回List
*/
private List<T> parseExcel(InputStream inputStream) {
return ExcelUtils.readExcel(inputStream);
}
/**
* 第二步: 校验Excel 标题行校验码
*/
protected abstract ExcelParseStatus headCheck(List<T> data);
/**
* 第三步: 数据校验和转换(由excel映射类转换成具体业务类)
*/
protected abstract ExcelParseResult<T, F> dataCheckAndTrans(List<T> data, ExcelImport excelImport);
/**
* 第四步: 数据入库
*/
protected abstract ExcelParseResult<T, F> saveData(ExcelParseResult<T, F> parseResult, ExcelImport excelImport);
}
- 具体子类
public class StudentExcelParseService extends AbstractExcelParseService<ExcelStudent, Student> {
@Override
protected ExcelParseStatus headCheck(List<ExcelStudent> data) {
/*
* 获取首行即标题行
*/
ExcelStudent excelStudent = data.get(0);
if (StringUtils.equals(excelStudent.getName(), "学生姓名")&&StringUtils.equals(excelStudent.getAge(), "年龄")){
return ExcelParseStatus.DEAL_SUCCESS;
}
return ExcelParseStatus.TEMPLATE_ERROR;
}
@Override
protected ExcelParseResult<ExcelStudent, Student> dataCheckAndTrans(List<ExcelStudent> data, ExcelImport excelImport) {
// 校验成功的数据
List<Student> successList = new ArrayList<>(1000);
List<ExcelStudent> errorList = new ArrayList<>(1000);
int totalCount = 0;
for (int i = 1; i < data.size(); i++) {
StringBuffer errorMessage = new StringBuffer();
ExcelStudent excelStudent = data.get(i);
String name = excelStudent.getName();
// 首列字段为空,不校验之后的数据
if (StringUtils.isBlank(name)){
break;
}
totalCount ++;
Student student = new Student();
student.setStudentId(UUID.randomUUID().toString());
student.setName(excelStudent.getName());
String age = excelStudent.getAge();
//TODO 这里可以校验一下 age 是不是数字并满足指定范围
errorMessage.append("校验错误说明,如果没有错误则为空");
student.setAge(Integer.parseInt(age));
student.setSex(Integer.parseInt(excelStudent.getSex()));
if (StringUtils.isBlank(errorMessage.toString())){
successList.add(student);
} else {
excelStudent.setErrorMessage(errorMessage.toString());
}
errorList.add(excelStudent);
}
if (totalCount == 0){
return ExcelParseResult.result(ExcelParseStatus.EXCEL_NO_DATA);
}
ExcelParseResult<ExcelStudent, Student> parseResult = new ExcelParseResult<>();
parseResult.setTotalCount(totalCount);
parseResult.setSuccessData(successList);
parseResult.setErrorData(errorList);
return parseResult;
}
@Override
protected ExcelParseResult<ExcelStudent, Student> saveData(ExcelParseResult<ExcelStudent, Student> parseResult, ExcelImport excelImport) {
List<Student> successData = parseResult.getSuccessData();
int totalCount = parseResult.getTotalCount();
int successCount = CollectionUtils.isEmpty(successData) ? 0: successData.size();
int errorCount = totalCount - successCount;
if (errorCount >0){
// TODO 生成带错误描述的excel供用户下载
}
if (successCount > 0) {
for (Student student : successData) {
// TODO 数据入库
}
excelImport.setTotalCount(totalCount);
excelImport.setSuccessCount(successCount);
excelImport.setErrorCount(successCount);
// TODO 更新excelImport
}
parseResult.setSuccessCount(successCount);
parseResult.setErrorCount(errorCount);
return parseResult;
}
}
- 具体子类
public class TeacherExcelParseService extends AbstractExcelParseService<ExcelTeacher, Teacher> {
@Override
protected ExcelParseStatus headCheck(List<ExcelTeacher> data) {
/*
* 获取首行即标题行
*/
ExcelTeacher excelTeacher = data.get(0);
if (StringUtils.equals(excelTeacher.getName(), "教师姓名")&&StringUtils.equals(excelTeacher.getClazz(), "班级")){
return ExcelParseStatus.DEAL_SUCCESS;
}
return ExcelParseStatus.TEMPLATE_ERROR;
}
@Override
protected ExcelParseResult<ExcelTeacher, Teacher> dataCheckAndTrans(List<ExcelTeacher> data, ExcelImport excelImport) {
// 校验成功的数据
List<Teacher> successList = new ArrayList<>(1000);
List<ExcelTeacher> errorList = new ArrayList<>(1000);
int totalCount = 0;
for (int i = 1; i < data.size(); i++) {
StringBuffer errorMessage = new StringBuffer();
ExcelTeacher excelTeacher = data.get(i);
String name = excelTeacher.getName();
// 首列字段为空,不校验之后的数据
if (StringUtils.isBlank(name)){
break;
}
totalCount ++;
Teacher Teacher = new Teacher();
Teacher.setTeacherId(UUID.randomUUID().toString());
Teacher.setName(excelTeacher.getName());
String clazz = excelTeacher.getClazz();
//TODO 这里可以校验一下 班级
errorMessage.append("校验错误说明,如果没有错误则为空");
Teacher.setClazz(clazz);
if (StringUtils.isBlank(errorMessage.toString())){
successList.add(Teacher);
} else {
excelTeacher.setErrorMessage(errorMessage.toString());
}
errorList.add(excelTeacher);
}
if (totalCount == 0){
return ExcelParseResult.result(ExcelParseStatus.EXCEL_NO_DATA);
}
ExcelParseResult<ExcelTeacher, Teacher> parseResult = new ExcelParseResult<>();
parseResult.setTotalCount(totalCount);
parseResult.setSuccessData(successList);
parseResult.setErrorData(errorList);
return parseResult;
}
@Override
protected ExcelParseResult<ExcelTeacher, Teacher> saveData(ExcelParseResult<ExcelTeacher, Teacher> parseResult, ExcelImport excelImport) {
List<Teacher> successData = parseResult.getSuccessData();
int totalCount = parseResult.getTotalCount();
int successCount = CollectionUtils.isEmpty(successData) ? 0: successData.size();
int errorCount = totalCount - successCount;
if (errorCount >0){
// TODO 生成带错误描述的excel供用户下载
}
if (successCount > 0) {
for (Teacher Teacher : successData) {
// TODO 数据入库
}
excelImport.setTotalCount(totalCount);
excelImport.setSuccessCount(successCount);
excelImport.setErrorCount(successCount);
// TODO 更新excelImport
}
parseResult.setSuccessCount(successCount);
parseResult.setErrorCount(errorCount);
return parseResult;
}
}
- 相关类
@Data
public class Student {
private String studentId;
private String name;
private int age;
private int sex;
}
@Data
public class ExcelStudent {
@ExcelProperty(index = 0)
private String name;
@ExcelProperty(index = 1)
private String age;
@ExcelProperty(index = 2)
private String sex;
private String errorMessage;
}
/**
* excel 文件对应的实体类
* @author xup
* @date 2019/11/13 16:31
*/
@Data
public class ExcelImport {
private String importId;
private String excelPath;
/**
* 业务类型
*/
private String type;
private int totalCount;
private int successCount;
private int errorCount;
private String dealResult;
}
/**
* Excel 解析状态
*
* @author xupeng03
* @version 创建时间:2019年9月3日
*/
public enum ExcelParseStatus {
/**
* 处理成功
*/
DEAL_SUCCESS("10", "处理成功"),
/**
* Excel 中无数据
*/
EXCEL_NO_DATA("11", "Excel中无数据"),
/**
* 待处理
*/
WAIT_TO_DEAL("00", "待处理"),
/**
* 处理失败
*/
DEAL_ERROR("21", "处理失败"),
/**
* 不符合模板
*/
TEMPLATE_ERROR("22", "不符合模板"),
/**
* 文件未找到
*/
EXCEL_NOT_FOUND("23", "文件未找到");
private String code;
private String message;
ExcelParseStatus(String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
}
/**
* 导入处理结果
*
* @author xupeng03
* @version 创建时间:2019年1月9日 下午3:43:59
*
*/
@Data
public class ExcelParseResult<T, F> {
private ExcelParseStatus parseStatus;
private int totalCount;
private int successCount;
private int errorCount;
private String message;
private List<F> successData;
private List<T> errorData;
public ExcelParseResult() {
super();
}
public ExcelParseResult(ExcelParseStatus parseStatus, String message) {
super();
this.parseStatus = parseStatus;
this.message = message;
}
public static <T,F> ExcelParseResult<T, F> result(ExcelParseStatus parseStatus){
return new ExcelParseResult<T, F>(parseStatus, parseStatus.getMessage());
}
}
/**
* excel 工具类
* @author xup
* @date 2019/11/13 15:25
*/
public class ExcelUtils {
public static <T> List<T> readExcel(InputStream inputStream){
ExcelAnalysisEventLister<T> analysisEventLister = new ExcelAnalysisEventLister<>();
EasyExcel.read(inputStream, analysisEventLister).sheet().doRead();
return analysisEventLister.getDatas();
}
}
/**
* Excel解析监听器
* 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,
* 然后里面用到spring可以构造方法传进去
* @author xup
* @date 2019/11/13 15:27
*/
public class ExcelAnalysisEventLister<T> extends AnalysisEventListener<T> {
private List<T> datas = new ArrayList<>(1000);
/**
* 这个每一条数据解析都会来调用
* @param object
* @param context
*/
@Override
public void invoke(T object, AnalysisContext context) {
datas.add(object);
}
/**
* 所有数据解析完成了 都会来调用
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
}
public List<T> getDatas() {
return datas;
}
}
扩展:excel解析可以结合策略模式和模板方法模式,根据不同的业务类型选择不同的excel解析处理类,不同的excel解析处理类又都继承抽象的模板方法类