写点什么

将设计模式应用到日常的 curd 中 - 模板方法和装饰器

用户头像
LSJ
关注
发布于: 2020 年 08 月 11 日

一千个读者就有一千个奥雷里亚诺,同样一千个coder就有几百种设计模式。只要在自己的项目中结合不同的业务场景,就能写出独一无二的设计代码。不用关心到底是生搬硬套还是另辟蹊径。做了不一定行,但是不做肯定不行,有时过程还是挺重要的。

利用模板方法消除模板代码



我在使用MyBatis做分页的时候,一般是这么写的,看起来有点繁琐,于是想着给简化一下代码行数。

PageHelper.startPage(pageNo,pageSize); // 1
List result = mapper.query(); // 2
PageInfo<T> pageInfo = new PageInfo<>(list); // 3
//自己定义的返回给客户端的对象
AppPageInfo<T> appPageInfo = new AppPageInfo<>(list,pageInfo.isHasNextPage()); //4

其实变化的部分只有第二行代码,剩下的可以被封装起来。可以利用模板方法做一些重构

定义函数接口



@FunctionalInterface
public interface PageQueryCallback<T> {
List<T> query();
}

定义execute方法



public class BaseService {
public <T> AppPageInfo<T> execute(PageQueryCallback<T> callback, PageReq req) {
PageHelper.startPage(req.getPageNo(),req.getPageSize());
List<T> list = callback.query();
PageInfo<T> pageInfo = new PageInfo<>(list);
AppPageInfo<T> appPageInfo = new AppPageInfo<>(list,pageInfo.isHasNextPage());
return appPageInfo;
}
}



这个方法可以放到BaseService中,作为通用方法供不同的子类调用。execute的第一个参数是刚定义的函数接口,第二个参数对象中只有两个属性pageNo和pageSize。同时该方法定义成泛型方法,这样就不会存在泛型类的约束,可以根据类型推断返回任何你想要的类型数据集合

execute方法调用



public AppPageInfo<Item> list(UserPageReq req) {
return execute(() -> queryList(req),req);
}

UserPageReq是PageReq的子类,可以添加更多的请求参数。



初次使用装饰器



装饰器在项目中的使用场景(看起来可能有些怪异):



当通过sql语句从数据库中查询列表数据时,列表中对象的属性没法通过一条sql语句搞定(或者由于这样做导致sql语句太复杂,横跨不同的业务范围再或者由于这些属性不在数据库中存储而是保存在Redis中)。基于此,从mysql中拿到原始数据后,需要在代码中设置另外的一些属性。比如在上一篇中分离关联查询

中讲到的,给结果集设置用户信息



使用模板方法时,如果想给结果追加用户相关属性代码是这样的:

public AppPageInfo<Info> queryInfoList(ListReq req) {
AppPageInfo<Info> pageInfo = execute(() -> queryList(req),req)); // 1
List<Info> list = pageInfo.getList(); // 2
userService.setUserBaseInfo(list);// 3
return pageInfo; //4
}



其实这种写法并没有多麻烦,但是如果有多处查询都需要追加用户属性时(事实证明在我们的项目中这样的场景有多达17处),这样的代码就会遍布各地,所以就琢磨着怎么写能更优雅一点。当然前期的思考难免会走一些弯路。不过后来想明白了,觉得装饰器其实还是蛮适合的。这就属于没条件也要自创条件地使用设计模式。在这里,装饰器是和刚定义的模板方法一起使用的。混搭竟产生了神奇的反应,也是始料未及的。当最终的代码呈现在眼中时,像是经过了一番打扮,竟觉得变漂亮了不少。



简单来说:装饰器分为被装饰者和装饰者,然后这两者都拥有共同的接口规范(目前我的用法就是这样的)



共同接口规范就是上面定义的PageQueryCallback接口,然后被装饰的部分就是我们上面代码中lambda表达式的部分。接下来我们可以为我们的结果集定义一个装饰类,拿追加用户信息的例子来说,可以定义成AppendUserInfo

public class AppendUserInfo<T> implements PageQueryCallback<T> {
private PageQueryCallback<T> callback;
private UserService userService;
@Override
public List<T> query() {
final List<T> list = callback.query();
userService.setUserBaseInfo((List<? extends SettingUserBaseInfoInterface>) list);
return list;
}
public AppendUserInfo(PageQueryCallback<T> callback) {
this.callback = callback;
this.userService = SpringContextUtil.getBean(UserService.class);
}
}



有了装饰类,再看如何使用

public AppPageInfo<Info> queryInfoList(ListReq req) {
return execute(new AppendUserInfo<>(()-> queryList(req),req));
}



对比两个queryInfoList方法,第二种写法确实简洁了不少。execute方法接受任何的PageQueryCallback类型参数,而我们的装饰器和被装饰者都实现此接口。



更进一步,除了给原始结果集设置用户属性外,还可以追加其他的属性,比如还可以给每一条数据追加点评论数据,这时候代码就像这样:



return execute(new AppendCommentInfo(new AppendUserInfo(() -> infoService.list(req))),req);

当然了,在这种场景下,要避免使用过多的装饰器,因为每一层装饰都涉及到最少一次和存储层的网络交互



发布于: 2020 年 08 月 11 日阅读数: 64
用户头像

LSJ

关注

微笑面对每一天 2018.11.11 加入

一个具有N年编程功力却早已拥有2N年工作经验的boy

评论

发布
暂无评论
将设计模式应用到日常的curd中-模板方法和装饰器