模板方法设计模式详解(结合文件上传)

1. 模板方法设计模式的基本概念和原理

1.1 定义

模板方法模式(Template Method Pattern)是一种行为设计模式,它在一个抽象类中定义了一个算法的骨架,而将一些步骤的具体实现延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些特定步骤。

1.2 核心思想

  • 算法骨架固定:在抽象类中定义算法的整体流程
  • 步骤可变:将算法中可变的部分抽象为抽象方法,由子类实现
  • 控制反转:父类调用子类的方法,而不是子类调用父类的方法

1.3 模式结构

AbstractClass (抽象类)
├── templateMethod() - 模板方法,定义算法骨架
├── primitiveOperation1() - 抽象方法,由子类实现
├── primitiveOperation2() - 抽象方法,由子类实现
└── hook() - 钩子方法(可选)

ConcreteClass (具体类)
├── primitiveOperation1() - 具体实现
└── primitiveOperation2() - 具体实现

2. 代码示例分析

2.1 抽象模板类:PictureUploadTemplate

PictureUploadTemplate 是图片上传的抽象模板类,定义了图片上传的完整流程:

public abstract class PictureUploadTemplate {
    
    // 模板方法:定义图片上传的完整算法流程
    public UploadPictureResult uploadPicture(Object inputSource, String uploadPathPrefix) {
        // 1. 校验图片
        validPicture(inputSource);
        
        // 2. 生成上传路径
        String uuid = RandomUtil.randomString(16);
        String originalFilename = getOriginFilename(inputSource);
        String uploadFilename = String.format("%s_%s.%s", 
            DateUtil.formatDate(new Date()), uuid, FileUtil.getSuffix(originalFilename));
        String uploadPath = String.format("/%s/%s", uploadPathPrefix, uploadFilename);
        
        File file = null;
        try {
            // 3. 创建临时文件
            file = File.createTempFile(uploadPath, null);
            
            // 4. 处理文件来源(抽象步骤)
            processFile(inputSource, file);
            
            // 5. 上传到对象存储
            PutObjectResult putObjectResult = cosManager.putPictureObject(uploadPath, file);
            
            // 6. 封装返回结果
            ImageInfo imageInfo = putObjectResult.getCiUploadResult()
                .getOriginalInfo().getImageInfo();
            return buildResult(originalFilename, file, uploadPath, imageInfo);
            
        } catch (Exception e) {
            log.error("图片上传到对象存储失败", e);
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "上传失败");
        } finally {
            // 7. 清理临时文件
            this.deleteTempFile(file);
        }
    }
    
    // 抽象方法:由子类实现具体的校验逻辑
    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 originalFilename, File file, 
                                          String uploadPath, ImageInfo imageInfo) {
        // 封装返回结果的具体实现...
    }
    
    // 具体方法:通用的文件清理逻辑
    public void deleteTempFile(File file) {
        // 删除临时文件的具体实现...
    }
}

2.2 具体实现类1:FilePictureUpload

FilePictureUpload 处理本地文件上传:

@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 * 1024;
        ThrowUtils.throwIf(fileSize > 2 * ONE_M, ErrorCode.PARAMS_ERROR, "文件大小不能超过 2MB");
        
        // 2. 校验文件后缀
        String fileSuffix = FileUtil.getSuffix(multipartFile.getOriginalFilename());
        final List<String> ALLOW_FORMAT_LIST = Arrays.asList("jpeg", "png", "jpg", "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.3 具体实现类2:UrlPictureUpload

UrlPictureUpload 处理URL图片上传:

@Service
public class UrlPictureUpload extends PictureUploadTemplate {

    @Override
    protected void validPicture(Object inputSource) {
        String fileUrl = (String) inputSource;
        
        // 1. 校验非空
        ThrowUtils.throwIf(StrUtil.isBlank(fileUrl), ErrorCode.PARAMS_ERROR, "文件地址为空");

        // 2. 校验URL格式
        try {
            new URL(fileUrl);
        } catch (MalformedURLException e) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件地址格式不正确");
        }
        
        // 3. 校验协议
        ThrowUtils.throwIf(!fileUrl.startsWith("http://") && !fileUrl.startsWith("https://"),
                ErrorCode.PARAMS_ERROR, "仅支持 HTTP 或 HTTPS 协议的文件地址");
        
        // 4. 发送HEAD请求验证文件存在性和类型
        HttpResponse httpResponse = null;
        try {
            httpResponse = HttpUtil.createRequest(Method.HEAD, fileUrl).execute();
            if (httpResponse.getStatus() != HttpStatus.HTTP_OK) {
                return;
            }
            
            // 5. 校验Content-Type
            String contentType = httpResponse.header("Content-Type");
            if (StrUtil.isNotBlank(contentType)) {
                final List<String> ALLOW_CONTENT_TYPES = Arrays.asList(
                    "image/jpeg", "image/jpg", "image/png", "image/webp");
                ThrowUtils.throwIf(!ALLOW_CONTENT_TYPES.contains(contentType.toLowerCase()),
                        ErrorCode.PARAMS_ERROR, "文件类型错误");
            }
            
            // 6. 校验文件大小
            String contentLengthStr = httpResponse.header("Content-Length");
            if (StrUtil.isNotBlank(contentLengthStr)) {
                try {
                    long contentLength = Long.parseLong(contentLengthStr);
                    final long ONE_M = 1024 * 1024;
                    ThrowUtils.throwIf(contentLength > 2 * ONE_M, 
                                     ErrorCode.PARAMS_ERROR, "文件大小不能超过 2MB");
                } catch (NumberFormatException e) {
                    throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件大小格式异常");
                }
            }
        } finally {
            if (httpResponse != null) {
                httpResponse.close();
            }
        }
    }

    @Override
    protected String getOriginFilename(Object inputSource) {
        String fileUrl = (String) inputSource;
        return FileUtil.mainName(fileUrl);
    }

    @Override
    protected void processFile(Object inputSource, File file) throws Exception {
        String fileUrl = (String) inputSource;
        // 下载文件到临时目录
        HttpUtil.downloadFile(fileUrl, file);
    }
}

3. 模板方法模式的实现方式和优点

3.1 实现方式分析

在这个图片上传系统中,模板方法模式的实现体现了以下特点:

  1. 算法骨架统一uploadPicture() 方法定义了完整的上传流程
  2. 变化点抽象:将不同输入源的处理逻辑抽象为三个方法
  3. 公共逻辑复用:文件路径生成、对象存储上传、结果封装等逻辑在父类中实现
  4. 扩展性良好:新增其他类型的图片上传只需继承并实现抽象方法

3.2 主要优点

  1. 代码复用:公共的算法流程只需在父类中实现一次
  2. 扩展性强:新增功能只需继承并实现特定方法
  3. 维护性好:算法结构集中管理,修改影响范围可控
  4. 符合开闭原则:对扩展开放,对修改封闭
  5. 控制反转:父类控制整体流程,子类专注于特定实现

3.3 在项目中的使用

PictureServiceImpl 中,根据输入源类型动态选择具体的实现:

// 根据 inputSource 的类型区分上传方式
PictureUploadTemplate pictureUploadTemplate = filePictureUpload;
if (inputSource instanceof String) {
    pictureUploadTemplate = urlPictureUpload;
}
UploadPictureResult uploadPictureResult = pictureUploadTemplate.uploadPicture(inputSource, uploadPathPrefix);

4. 模板方法在实际开发中的应用场景

4.1 常见应用场景

  1. 数据处理流程:ETL(提取、转换、加载)过程
  2. 文件处理:不同格式文件的读取、解析、转换
  3. 网络请求处理:HTTP请求的预处理、处理、后处理
  4. 测试框架:测试用例的初始化、执行、清理
  5. 游戏开发:游戏关卡的加载、运行、结束流程
  6. 报表生成:数据获取、格式化、输出的统一流程

4.2 实际项目中的扩展示例

基于当前的图片上传模板,可以轻松扩展支持其他类型:

// Base64图片上传
@Service
public class Base64PictureUpload extends PictureUploadTemplate {
    @Override
    protected void validPicture(Object inputSource) {
        // Base64格式校验逻辑
    }
    
    @Override
    protected String getOriginFilename(Object inputSource) {
        // 从Base64数据中提取文件名
    }
    
    @Override
    protected void processFile(Object inputSource, File file) throws Exception {
        // Base64解码并写入文件
    }
}

// FTP服务器图片上传
@Service
public class FtpPictureUpload extends PictureUploadTemplate {
    @Override
    protected void validPicture(Object inputSource) {
        // FTP连接和文件存在性校验
    }
    
    @Override
    protected String getOriginFilename(Object inputSource) {
        // 从FTP路径中提取文件名
    }
    
    @Override
    protected void processFile(Object inputSource, File file) throws Exception {
        // 从FTP服务器下载文件
    }
}

5. 与其他设计模式的对比

5.1 模板方法 vs 策略模式

特性 模板方法模式 策略模式
目的 定义算法骨架,子类实现具体步骤 定义算法族,运行时选择算法
结构 继承关系,父类定义流程 组合关系,客户端选择策略
变化点 算法中的某些步骤 整个算法
控制权 父类控制流程 客户端控制选择
扩展方式 继承父类 实现策略接口

示例对比

  • 模板方法:图片上传流程固定,但文件来源处理方式不同
  • 策略模式:排序算法完全不同(快排、归并、堆排序)

5.2 模板方法 vs 工厂方法

特性 模板方法模式 工厂方法模式
关注点 算法流程的复用 对象创建的封装
变化内容 算法步骤的实现 创建的产品类型
使用时机 有固定流程但步骤实现不同 需要创建不同类型的对象

5.3 模板方法 vs 观察者模式

特性 模板方法模式 观察者模式
通信方式 父类调用子类方法 主题通知观察者
耦合度 紧耦合(继承关系) 松耦合(接口关系)
适用场景 算法流程固定 状态变化通知

6. 最佳实践和注意事项

6.1 设计原则

  1. 单一职责:每个抽象方法只负责一个特定的步骤
  2. 开闭原则:对扩展开放,对修改封闭
  3. 里氏替换:子类可以替换父类使用
  4. 依赖倒置:依赖抽象而不是具体实现

6.2 实现建议

  1. 合理划分步骤:将算法分解为合适粒度的步骤
  2. 提供钩子方法:为可选步骤提供默认实现
  3. 异常处理统一:在模板方法中统一处理异常
  4. 文档完善:清晰说明每个抽象方法的职责和约定

6.3 潜在问题

  1. 继承层次复杂:过多的继承可能导致类层次复杂
  2. 调试困难:父类调用子类方法可能增加调试难度
  3. 灵活性限制:算法骨架固定,难以进行大幅调整

7. 总结

模板方法模式是一种非常实用的设计模式,特别适合于有固定流程但具体实现步骤可能不同的场景。在图片上传系统中的应用很好地展示了该模式的优势:

  1. 统一流程:所有图片上传都遵循相同的处理流程
  2. 灵活扩展:可以轻松支持新的图片来源类型
  3. 代码复用:公共逻辑得到最大化复用
  4. 维护简单:算法结构清晰,易于理解和维护

通过合理使用模板方法模式,可以构建出既稳定又灵活的系统架构,为后续的功能扩展和维护奠定良好的基础。

#设计模式##文件上传#
全部评论

相关推荐

Twilight_m...:还是不够贴近现实,中关村那块60平房子200万怎么可能拿的下来,交个首付还差不多
点赞 评论 收藏
分享
05-22 17:07
已编辑
门头沟学院 Java
程序员牛肉:都啥时候了还jb打蓝桥杯呢,有限找实习。
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务