设计模式:为什么使用模板设计模式(不相同的步骤进行抽取,使用不同的子类实现)减少重复代码,让代码更好维护。

目录

  1. 问题是什么,为什么使用模板方法
  2. 模板设计模式是什么
  3. 实际的应用场景

一、问题

kt 复制代码
// 咖啡制作
class CoffeeMaker {
    fun makeCoffee() {
        boilWater()
        println("Brewing coffee grounds")
        pourInCup()
        println("Adding sugar and milk")
    }

    private fun boilWater() = println("Boiling water")
    private fun pourInCup() = println("Pouring into cup")
}

// 茶制作
class TeaMaker {
    fun makeTea() {
        boilWater()
        println("Steeping tea bag")
        pourInCup()
        println("Adding lemon")
    }

    private fun boilWater() = println("Boiling water")
    private fun pourInCup() = println("Pouring into cup")
}

问题分析:

❌ 重复代码:boilWater() 和 pourInCup() 重复

❌ 维护困难:修改流程需要改动所有实现类

这里,我们就需要引出我们今天的主角,模板设计模式。


二、模板设计模式是什么

在父类中定义流程步骤,允许子类在不改变原有步骤的情况下重写特定步骤,下面直接上代码。

kt 复制代码
// 抽象模板类
abstract class BeverageMaker {
    // 模板方法(final禁止子类覆盖)
    final fun prepareBeverage() {
        boilWater()
        brew()
        pourInCup()
        addCondiments()
    }

    // 具体步骤实现
    private fun boilWater() = println("Boiling water")
    private fun pourInCup() = println("Pouring into cup")

    // 抽象方法(必须由子类实现)
    abstract fun brew()
    abstract fun addCondiments()
}

// 具体实现类
class CoffeeMaker : BeverageMaker() {
    override fun brew() = println("Brewing coffee grounds")
    override fun addCondiments() = println("Adding sugar and milk")
}

class TeaMaker : BeverageMaker() {
    override fun brew() = println("Steeping tea bag")
    override fun addCondiments() = println("Adding lemon")
}

// 使用示例
fun main() {
    val coffee = CoffeeMaker()
    coffee.prepareBeverage()
    
    println("\n---------------\n")
    
    val tea = TeaMaker()
    tea.prepareBeverage()
}

可以看到,我们只需要使用抽象父类,子类进行具体实现,就可以完成模板设计模式。

带来的好处就是,重复代码没了。

2.1 接下来我们看一个实际的应用场景

我们有一个图片文件上传功能和图片url上传功能,这个功能里面,大部分逻辑是一样的,只有特定的不一样,我们看看。

图片文件上传功能

java 复制代码
public UploadPictureResult uploadPicture(MultipartFile multipartFile, String uploadPathPrefix) {

    //校验图片:大小
    validPicture(multipartFile);
    //图片上传地址
    //1. 业务上传图片区分   2. 随机数 3.原始文件名称。使用原始文件名称会存在安全性问题
    //2. 文件前缀
    String uuid = RandomUtil.randomNumbers(16);
    String originalFilename = multipartFile.getOriginalFilename();
    String uploadFilename = String.format("%s_%s_%s", DateUtil.formatDate(new Date()), uuid,
            originalFilename);
    String uploadPath = String.format("/%s/%s",uploadPathPrefix,uploadFilename);

    //然后发送给对象存储
    File tempFile =null;
    try {
        //解析结果并返回
        tempFile = File.createTempFile(uploadPath, null);
        multipartFile.transferTo(tempFile);
        PutObjectResult putObjectResult = cosManager.putPictureObject(uploadPath, tempFile);
        ImageInfo imageInfo = putObjectResult.getCiUploadResult().getOriginalInfo().getImageInfo();
        int width = imageInfo.getWidth();
        int height = imageInfo.getHeight();
        double picScale = NumberUtil.round(width *1.0 /height,2).doubleValue();
        UploadPictureResult uploadPictureResult = new UploadPictureResult();
        uploadPictureResult.setUrl(cosClientConfig.getHost()+"/"+uploadPath);
        uploadPictureResult.setPicName(FileUtil.mainName(originalFilename));
        uploadPictureResult.setPicSize(FileUtil.size(tempFile));
        uploadPictureResult.setPicWidth(width);
        uploadPictureResult.setPicHeight(height);
        uploadPictureResult.setPicScale(picScale);
        uploadPictureResult.setPicFormat(imageInfo.getFormat());

        return uploadPictureResult;
    } catch (Exception e) {
        log.error("file upload error,filepath = "+ uploadPath,e);
        throw new BusinessException(ErrorCode.SYSTEM_ERROR,"上传失败");
    }finally {
        //临时文件清理
        deleteTempFile(tempFile);
    }


}

图片url上传功能

java 复制代码
public UploadPictureResult uploadPictureByUrl(String fileUrl, String uploadPathPrefix) {
    //校验图片:大小
    //validPicture(multipartFile);
    //todo
    validPicture(fileUrl);

    //图片上传地址
    //1. 业务上传图片区分   2. 随机数 3.原始文件名称。使用原始文件名称会存在安全性问题
    //2. 文件前缀
    String uuid = RandomUtil.randomNumbers(16);
    //todo 获取图片原始名字
    //String originalFilename = multipartFile.getOriginalFilename();
    String originalFilename = FileUtil.mainName(fileUrl);
    String uploadFilename = String.format("%s_%s_%s", DateUtil.formatDate(new Date()), uuid,
            originalFilename);
    String uploadPath = String.format("/%s/%s",uploadPathPrefix,uploadFilename);

    //然后发送给对象存储
    File tempFile =null;
    try {
        //解析结果并返回
        tempFile = File.createTempFile(uploadPath, null);
        //todo 下载文件
        //multipartFile.transferTo(tempFile);
        HttpUtil.downloadFile(fileUrl,tempFile);
        PutObjectResult putObjectResult = cosManager.putPictureObject(uploadPath, tempFile);
        ImageInfo imageInfo = putObjectResult.getCiUploadResult().getOriginalInfo().getImageInfo();
        int width = imageInfo.getWidth();
        int height = imageInfo.getHeight();
        double picScale = NumberUtil.round(width *1.0 /height,2).doubleValue();
        UploadPictureResult uploadPictureResult = new UploadPictureResult();
        uploadPictureResult.setUrl(cosClientConfig.getHost()+"/"+uploadPath);
        uploadPictureResult.setPicName(FileUtil.mainName(originalFilename));
        uploadPictureResult.setPicSize(FileUtil.size(tempFile));
        uploadPictureResult.setPicWidth(width);
        uploadPictureResult.setPicHeight(height);
        uploadPictureResult.setPicScale(picScale);
        uploadPictureResult.setPicFormat(imageInfo.getFormat());

        return uploadPictureResult;
    } catch (Exception e) {
        log.error("file upload error,filepath = "+ uploadPath,e);
        throw new BusinessException(ErrorCode.SYSTEM_ERROR,"上传失败");
    }finally {
        //临时文件清理
        deleteTempFile(tempFile);
    }
}

这两份代码里面,只有三个地方不一样:

  1. validPicture(multipartFile);
  2. String originalFilename = multipartFile.getOriginalFilename();
  3. multipartFile.transferTo(tempFile);

其他的都是一样,我们可以使用模板方法进行抽象,但是这里有个问题就是,有个参数是String,有个参数是MultipartFile,类型不一样,怎么办呢?我们可以使用泛型或者Object来解决,这里我们就使用Object。


2.1.1 抽象一个父类

这个父类里面,有一个公共方法uploadPicture,参数为object,有三个抽象方法:validPicture、getOriginFilename以及processFile。

java 复制代码
@Slf4j
public abstract class PictureUploadTemplate {  
  
    @Resource
    protected CosManager cosManager;
  
    @Resource  
    protected CosClientConfig cosClientConfig;
  
    /**  
     * 模板方法,定义上传流程  
     */  
    public final UploadPictureResult uploadPicture(Object inputSource, String uploadPathPrefix) {
        // 1. 校验图片  
        validPicture(inputSource);  
  
        // 2. 图片上传地址  
        String uuid = RandomUtil.randomString(16);
        String originFilename = getOriginFilename(inputSource);  
        String uploadFilename = String.format("%s_%s.%s", DateUtil.formatDate(new Date()), uuid,
                FileUtil.getSuffix(originFilename));
        String uploadPath = String.format("/%s/%s", uploadPathPrefix, uploadFilename);  
  
        File file = null;  
        try {  
            // 3. 创建临时文件  
            file = File.createTempFile(uploadPath, null);
            // 处理文件来源(本地或 URL)  
            processFile(inputSource, file);  
  
            // 4. 上传图片到对象存储  
            PutObjectResult putObjectResult = cosManager.putPictureObject(uploadPath, file);
            ImageInfo imageInfo = putObjectResult.getCiUploadResult().getOriginalInfo().getImageInfo();
  
            // 5. 封装返回结果  
            return buildResult(originFilename, file, uploadPath, imageInfo);  
        } catch (Exception e) {  
            log.error("图片上传到对象存储失败", e);  
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "上传失败");
        } finally {  
            // 6. 清理临时文件  
            deleteTempFile(file);  
        }  
    }  
  
    /**  
     * 校验输入源(本地文件或 URL)  
     */  
    protected abstract void validPicture(Object inputSource);  
  
    /**  
     * 获取输入源的原始文件名  
     */  
    protected abstract String getOriginFilename(Object inputSource);  
  
    /**  
     * 处理输入源并生成本地临时文件  
     */  
    protected abstract void processFile(Object inputSource, File file) throws Exception;  
  
    /**  
     * 封装返回结果  
     */  
    private UploadPictureResult buildResult(String originFilename, File file, String uploadPath, ImageInfo imageInfo) {  
        UploadPictureResult uploadPictureResult = new UploadPictureResult();  
        int picWidth = imageInfo.getWidth();  
        int picHeight = imageInfo.getHeight();  
        double picScale = NumberUtil.round(picWidth * 1.0 / picHeight, 2).doubleValue();
        uploadPictureResult.setPicName(FileUtil.mainName(originFilename));  
        uploadPictureResult.setPicWidth(picWidth);  
        uploadPictureResult.setPicHeight(picHeight);  
        uploadPictureResult.setPicScale(picScale);  
        uploadPictureResult.setPicFormat(imageInfo.getFormat());  
        uploadPictureResult.setPicSize(FileUtil.size(file));  
        uploadPictureResult.setUrl(cosClientConfig.getHost() + "/" + uploadPath);  
        return uploadPictureResult;  
    }  
  
    /**  
     * 删除临时文件  
     */  
    public void deleteTempFile(File file) {  
        if (file == null) {  
            return;  
        }  
        boolean deleteResult = file.delete();  
        if (!deleteResult) {  
            log.error("file delete error, filepath = {}", file.getAbsolutePath());  
        }  
    }  
}

2.1.2 子类实现

java 复制代码
@Service
public class UrlPictureUpload extends PictureUploadTemplate {  
    @Override  
    protected void validPicture(Object inputSource) {  
        String fileUrl = (String) inputSource;  
        ThrowUtils.throwIf(StrUtil.isBlank(fileUrl), ErrorCode.PARAMS_ERROR, "文件地址不能为空");
        // ... 跟之前的校验逻辑保持一致  
    }  
  
    @Override  
    protected String getOriginFilename(Object inputSource) {  
        String fileUrl = (String) inputSource;  
        // 从 URL 中提取文件名  
        return FileUtil.mainName(fileUrl);
    }  
  
    @Override  
    protected void processFile(Object inputSource, File file) throws Exception {
        String fileUrl = (String) inputSource;  
        // 下载文件到临时目录  
        HttpUtil.downloadFile(fileUrl, file);
    }  
}
java 复制代码
@Service
public class FilePictureUpload extends PictureUploadTemplate {  
  
    @Override  
    protected void validPicture(Object inputSource) {  
        MultipartFile multipartFile = (MultipartFile) inputSource;
        ThrowUtils.throwIf(multipartFile == null, ErrorCode.PARAMS_ERROR, "文件不能为空");
        // 1. 校验文件大小  
        long fileSize = multipartFile.getSize();  
        final long ONE_M = 1024 * 1024L;  
        ThrowUtils.throwIf(fileSize > 2 * ONE_M, ErrorCode.PARAMS_ERROR, "文件大小不能超过 2M");  
        // 2. 校验文件后缀  
        String fileSuffix = FileUtil.getSuffix(multipartFile.getOriginalFilename());
        // 允许上传的文件后缀  
        final List<String> ALLOW_FORMAT_LIST = Arrays.asList("jpeg", "jpg", "png", "webp");
        ThrowUtils.throwIf(!ALLOW_FORMAT_LIST.contains(fileSuffix), ErrorCode.PARAMS_ERROR, "文件类型错误");  
    }  
  
    @Override  
    protected String getOriginFilename(Object inputSource) {  
        MultipartFile multipartFile = (MultipartFile) inputSource;  
        return multipartFile.getOriginalFilename();  
    }  
  
    @Override  
    protected void processFile(Object inputSource, File file) throws Exception {
        MultipartFile multipartFile = (MultipartFile) inputSource;  
        multipartFile.transferTo(file);  
    }  
}

2.1.3 进行使用

java 复制代码
@Resource
private FilePictureUpload filePictureUpload;

@Resource
private UrlPictureUpload urlPictureUpload;

@Override
public PictureVO uploadPicture(Object inputSource, PictureUploadRequest pictureUploadRequest, User loginUser) {

    PictureUploadTemplate pictureUploadTemplate = filePictureUpload;
    if (inputSource instanceof String){
        pictureUploadTemplate = urlPictureUpload;
    }
    UploadPictureResult uploadPictureResult = pictureUploadTemplate.uploadPicture(inputSource, uploadPathPrefix);

}

通过判断inputSource不同的类型来使用不同的模板子类

相关推荐
浙江第二深情几秒前
前端性能优化终极指南
java·maven
不知名用户来了2 分钟前
基于vue3 封装的antdv/element-Plus 快速生成增删改查页面
前端
明川8 分钟前
Android Gradle - ASM + AsmClassVisitorFactory插桩使用
android·前端·gradle
布列瑟农的星空9 分钟前
webpack迁移rsbuild——配置深度对比
前端
前端小黑屋11 分钟前
查看项目中无引用到的文件、函数
前端
前端小黑屋12 分钟前
小程序直播挂件Pendant问题
前端·微信小程序·直播
养乐多072216 分钟前
【Java】IO流
java
俊男无期16 分钟前
超效率工作法
java·前端·数据库
LYFlied16 分钟前
【每日算法】LeetCode 46. 全排列
前端·算法·leetcode·面试·职场和发展
刘一说20 分钟前
Vue Router:官方路由解决方案解析
前端·javascript·vue.js