查看原文
其他

把「策略模式」应用到实际项目中

flyhero 码上实战 2022-09-08


无论你知不知道这个设计模式,但必定在项目中都似曾相识。倘若仅仅聊理论必然枯燥乏味,只有理论和实战相结合方可达到人剑合一的境界。



首先,我来说个需求,倘若是你遇到该如何做?你可停留几分钟,想出你的解决方式,可在下方留言,说出你的想法。






需求

用户有文件上传的需求,而我们要负责对文件进行存储,由于我们的系统可能会单独给个别客户私有化部署(部署尽量少依赖中间件能服务等),同时我们也会自己运营成为自己的SaaS服务(保证服务的高可用等)。

所以我们有两点需求:

1. 要在SaaS版本中将文件存入分布式存储系统fastDfs中

2. 客户的私有部署中将文件存储在数据库中



思路

寻找异同点

  • 文件上传过程这个无论什么版本部署都是一样的,所以暂不考虑。

  •  文件存储的方式不同,同时文件的获取和删除也不同

  • 保存,获取,删除后的响应也是相同的,也不考虑了。


开始抽象

当前我们考虑的就是文件的存储,获取和删除。同样的行为,不同的实现,我们必定想到定义一个接口:

  1. /**

  2. * 文件存储接口

  3. * Identify表示文件的唯一标识,可任意类型

  4. * T 表示 上传下载的返回类型,可任意类型

  5. *

  6. * @author flyhero

  7. * @date 2019-02-01 11:18 AM

  8. */

  9. public interface IStorageService<Identify, T> {


  10. /**

  11. * 上传文件

  12. *

  13. * @param file

  14. * @return

  15. */

  16. T upload(MultipartFile file);


  17. /**

  18. * 下载文件

  19. *

  20. * @param identify

  21. * @return

  22. */

  23. T download(Identify identify);


  24. /**

  25. * 删除文件

  26. *

  27. * @param identify

  28. */

  29. void delete(Identify identify);

  30. }

两种不同的实现

  • 存储FastDfs

  1. @Slfj

  2. @Service("fastDfsServiceImpl")

  3. public class FastDfsServiceImpl implements IStorageService<String,FileVo> {


  4. @Override

  5. @Transactional(rollbackFor = Exception.class)

  6. public FileVo upload(MultipartFile multipartFile){

  7. logger.info("存储在fastDfs……");

  8. }


  9. @Override

  10. public FileVo download(String hash) {

  11. logger.info("从fastDfs下载文件");

  12. return null;

  13. }


  14. @Override

  15. @Transactional(rollbackFor = Exception.class)

  16. public void delete(String hash) {

  17. logger.info("从fastDfs删除文件");

  18. }

  19. }

  • 存储 数据库

  1. @Slfj

  2. @Service("databaseServiceImpl")

  3. public class DatabaseServiceImpl implements IStorageService<String,FileVo> {


  4. @Override

  5. @Transactional(rollbackFor = Exception.class)

  6. public FileVo upload(MultipartFile file) {

  7. logger.info("存储在database……");

  8. return null;

  9. }


  10. @Override

  11. public FileVo download(String hash) {

  12. logger.info("从database下载文件");

  13. return null;

  14. }


  15. @Override

  16. public void delete(String hash) {

  17. logger.info("从database删除文件");

  18. }

  19. }

  • 调用

  1. @Service

  2. public class FileServiceImpl implements FileService {


  3. // 同一个接口根据不同的名称调用不同的实现

  4. // @Qualifier("databaseServiceImpl")

  5. @Qualifier("fastDfsServiceImpl")

  6. @Autowired

  7. private IStorageService storageService;


  8. public void save(MultipartFile file){

  9. if (null == file) {

  10. throws new Exception("文件不能为空");

  11. }

  12. FileVo fileVo = storageService.upload(file);

  13. }

  14. }



疑惑

可能有人会说了:这怎么和我了解的策略模式不一样啊,策略模式不是这样的吗?

你说的对!但这是在没有任何框架下的设计模式,而我们现在普遍使用的都是Spring框架,那么标准的策略模式中的Context去哪里了?


  • 引入Context的作用

首先我们要知道引入Context的作用,是为了避免高层直接与策略接口直接交互,为什么呢?因为我们策略模式接口功能相对比较单一,而有些高层模块可能需要一些比较复杂的交互。


   1.若直接调用接口,则需要对每个实现增加逻辑;

   2.若直接调用前,执行增强逻辑,那么多个地方使用时,会存在重复增强逻辑,并可能忘掉。

此时引入Context是解决问题的最佳方式。

而我们的FileServiceImpl就相当于Context的作用,由于我们使用Spring框架且使用了三层架构,暴露上传文件、下载文件、删除文件是在controller层三个不同的方法中(或不同的controller中),为了避免几个地方使用的存储策略不同,我直接在Context中来指定使用的策略,当需要切换时也非常方便,只需要更改IStorageService的注解即可。



总结

优点

  • 策略实现类可自由切换

  • 易于扩展,若还有新的策略只要新增策略接口一个实现类即可

  • 不必使用条件语句进行决定使用哪个策略


缺点

  • 策略类一旦增多,调用者要清楚的知道各种策略的区别


如果你有不同的看法,还望不吝指教。



海内存知己,天涯若比邻,喜欢就关注吧


喜欢就点击“好看”吧

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存