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

目录

  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不同的类型来使用不同的模板子类

相关推荐
穿林鸟2 分钟前
Spring Boot项目信创国产化适配指南
java·spring boot·后端
此木|西贝22 分钟前
【设计模式】模板方法模式
java·设计模式·模板方法模式
wapicn9931 分钟前
手机归属地查询Api接口,数据准确可靠
java·python·智能手机·php
Thread.sleep(0)33 分钟前
WebRTC源码解析:Android如何渲染画面
android·webrtc
hycccccch1 小时前
Springcache+xxljob实现定时刷新缓存
java·后端·spring·缓存
wisdom_zhe1 小时前
Spring Boot 日志 配置 SLF4J 和 Logback
java·spring boot·logback
揣晓丹1 小时前
JAVA实战开源项目:校园失物招领系统(Vue+SpringBoot) 附源码
java·开发语言·vue.js·spring boot·开源
Android 小码峰啊2 小时前
Android Dagger 2 框架的注解模块深入剖析 (一)
android·adb·android studio·android-studio·androidx·android runtime
于过2 小时前
Spring注解编程模型
java·后端
北随琛烬入2 小时前
Spark(10)配置Hadoop集群-集群配置
java·hadoop·spark