模板方法设计模式详解(结合文件上传)
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 实现方式分析
在这个图片上传系统中,模板方法模式的实现体现了以下特点:
- 算法骨架统一:
uploadPicture()
方法定义了完整的上传流程 - 变化点抽象:将不同输入源的处理逻辑抽象为三个方法
- 公共逻辑复用:文件路径生成、对象存储上传、结果封装等逻辑在父类中实现
- 扩展性良好:新增其他类型的图片上传只需继承并实现抽象方法
3.2 主要优点
- 代码复用:公共的算法流程只需在父类中实现一次
- 扩展性强:新增功能只需继承并实现特定方法
- 维护性好:算法结构集中管理,修改影响范围可控
- 符合开闭原则:对扩展开放,对修改封闭
- 控制反转:父类控制整体流程,子类专注于特定实现
3.3 在项目中的使用
在 PictureServiceImpl
中,根据输入源类型动态选择具体的实现:
// 根据 inputSource 的类型区分上传方式
PictureUploadTemplate pictureUploadTemplate = filePictureUpload;
if (inputSource instanceof String) {
pictureUploadTemplate = urlPictureUpload;
}
UploadPictureResult uploadPictureResult = pictureUploadTemplate.uploadPicture(inputSource, uploadPathPrefix);
4. 模板方法在实际开发中的应用场景
4.1 常见应用场景
- 数据处理流程:ETL(提取、转换、加载)过程
- 文件处理:不同格式文件的读取、解析、转换
- 网络请求处理:HTTP请求的预处理、处理、后处理
- 测试框架:测试用例的初始化、执行、清理
- 游戏开发:游戏关卡的加载、运行、结束流程
- 报表生成:数据获取、格式化、输出的统一流程
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 设计原则
- 单一职责:每个抽象方法只负责一个特定的步骤
- 开闭原则:对扩展开放,对修改封闭
- 里氏替换:子类可以替换父类使用
- 依赖倒置:依赖抽象而不是具体实现
6.2 实现建议
- 合理划分步骤:将算法分解为合适粒度的步骤
- 提供钩子方法:为可选步骤提供默认实现
- 异常处理统一:在模板方法中统一处理异常
- 文档完善:清晰说明每个抽象方法的职责和约定
6.3 潜在问题
- 继承层次复杂:过多的继承可能导致类层次复杂
- 调试困难:父类调用子类方法可能增加调试难度
- 灵活性限制:算法骨架固定,难以进行大幅调整
7. 总结
模板方法模式是一种非常实用的设计模式,特别适合于有固定流程但具体实现步骤可能不同的场景。在图片上传系统中的应用很好地展示了该模式的优势:
- 统一流程:所有图片上传都遵循相同的处理流程
- 灵活扩展:可以轻松支持新的图片来源类型
- 代码复用:公共逻辑得到最大化复用
- 维护简单:算法结构清晰,易于理解和维护
通过合理使用模板方法模式,可以构建出既稳定又灵活的系统架构,为后续的功能扩展和维护奠定良好的基础。
#设计模式##文件上传#