共计 2484 个字符,预计需要花费 7 分钟才能阅读完成。
阅读原文:把「策略模式」应用到实际项目中
无论你知不知道这个设计模式,但必定在项目中都似曾相识。倘若仅仅聊理论必然枯燥乏味,只有理论和实战相结合方可达到人剑合一的境界。
首先,我来说个需求,倘若是你遇到该如何做?你可停留几分钟,想出你的解决方式,可在下方留言,说出你的想法。
需求
用户有文件上传的需求,而我们要负责对文件进行存储,由于我们的系统可能会单独给个别客户私有化部署(部署尽量少依赖中间件能服务等),同时我们也会自己运营成为自己的 SaaS 服务(保证服务的高可用等)。
所以我们有两点需求:
- 要在 SaaS 版本中将文件存入分布式存储系统 fastDfs 中
- 客户的私有部署中将文件存储在数据库中
思路
寻找异同点
- 文件上传过程这个无论什么版本部署都是一样的,所以暂不考虑。
- 文件存储的方式不同,同时文件的获取和删除也不同
- 保存,获取,删除后的响应也是相同的,也不考虑了。
开始抽象
当前我们考虑的就是文件的存储,获取和删除了。同样的行为,不同的实现,我们必定想到定义一个接口:
/**
* 文件存储接口
* Identify 表示文件的唯一标识,可任意类型
* T 表示 上传下载的返回类型,可任意类型
*
* @author flyhero
* @date 2019-02-01 11:18 AM
*/
public interface IStorageService<Identify, T> {
/**
* 上传文件
*
* @param file
* @return
*/
T upload(MultipartFile file);
/**
* 下载文件
*
* @param identify
* @return
*/
T download(Identify identify);
/**
* 删除文件
*
* @param identify
*/
void delete(Identify identify);
}
两种不同的实现
- 存储 FastDfs
@Slfj
@Service("fastDfsServiceImpl")
public class FastDfsServiceImpl implements IStorageService<String,FileVo> {
@Override
@Transactional(rollbackFor = Exception.class)
public FileVo upload(MultipartFile multipartFile){logger.info("存储在 fastDfs……");
}
@Override
public FileVo download(String hash) {logger.info("从 fastDfs 下载文件");
return null;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(String hash) {logger.info("从 fastDfs 删除文件");
}
}
- 存储 数据库
@Slfj
@Service("databaseServiceImpl")
public class DatabaseServiceImpl implements IStorageService<String,FileVo> {
@Override
@Transactional(rollbackFor = Exception.class)
public FileVo upload(MultipartFile file) {logger.info("存储在 database……");
return null;
}
@Override
public FileVo download(String hash) {logger.info("从 database 下载文件");
return null;
}
@Override
public void delete(String hash) {logger.info("从 database 删除文件");
}
}
- 调用
@Service
public class FileServiceImpl implements FileService {
// 同一个接口根据不同的名称调用不同的实现
// @Qualifier("databaseServiceImpl")
@Qualifier("fastDfsServiceImpl")
@Autowired
private IStorageService storageService;
public void save(MultipartFile file){if (null == file) {throws new Exception("文件不能为空");
}
FileVo fileVo = storageService.upload(file);
}
}
疑惑
可能有人会说了:这怎么和我了解的策略模式不一样啊,策略模式不是这样的吗?
你说的对!但这是在没有任何框架下的设计模式,而我们现在普遍使用的都是 Spring 框架,那么标准的策略模式中的 Context 去哪里了?
- 引入 Context 的作用
首先我们要知道引入 Context 的作用,是为了避免高层直接与策略接口直接交互,为什么呢?因为我们策略模式接口功能相对比较单一,而有些高层模块可能需要一些比较复杂的交互。
- 若直接调用接口,则需要对每个实现增加逻辑;
- 若直接调用前,执行增强逻辑,那么多个地方使用时,会存在重复增强逻辑,并可能忘掉。
此时引入 Context 是解决问题的最佳方式。
而我们的 FileServiceImpl 就相当于 Context 的作用,由于我们使用 Spring 框架且使用了三层架构,暴露上传文件、下载文件、删除文件是在 controller 层三个不同的方法中(或不同的 controller 中),为了避免几个地方使用的存储策略不同,我直接在 Context 中来指定使用的策略,当需要切换时也非常方便,只需要更改 IStorageService 的注解即可。
总结
优点
- 策略实现类可自由切换
- 易于扩展,若还有新的策略只要新增策略接口一个实现类即可
- 不必使用条件语句进行决定使用哪个策略
缺点
- 策略类一旦增多,调用者要清楚的知道各种策略的区别
如果你有不同的看法,还望不吝指教。
更多精彩技术文章尽在微信公众号:码上实战