设计模式实战之模板方法

540 阅读6分钟

模板方法模式

模板方法模式定义了一个算法的步骤,并允许子类为一个或多个步骤提供具体实现.让子类在不改变算法架构的情况下,重新定义算法中的某些步骤.

定义特定方法(一般为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解析处理类又都继承抽象的模板方法类