背 景
“明天周五,下周二要上线?” 、“有没有搞错!今天搞 openAPI,明天搞数据中台,计划要搞营销中台”、“1个人干 6 个人的活,到是给我发 7 个人的工资啊 ”
二黄啃完剩下的面包,打开桌面上的《订单数据对外接口需求文档》; 新的一天,在一万只草泥马奔腾后,悄然拉开序幕。
需求理解
- open API 1.0 第一个版本,暂不上签名与加密、流量控制等非功能性需求
- 开放一个订单开票数量查询接口,方便兄弟团队通过 http 请求获取订单交易量,示例如下:
请求:http://data.xiaoh.com/api/orderQty?periodId=201906
响应:
{
"status": 0,"msg": "success","apiName": "orderQty",
"data": {
"result": [{
"area": "EU",
"orgId": "Eastern Europe",
"periodId": "20190618",
"billingQty": 10086
}, {
"area": "Asia",
"orgId": "Eastern Europe",
"periodId": "20190618",
"billingQty": 10086
}],
"hasNext": false,"totalSize": 7,"pageNum": 1
}
}
话说 openAPI、数据中台营销中台,在二黄看来无非就是升级版的 rest API ,
openAPI1.0 面向兄弟团队提供数据服务,暂时不上安全等组件,因此跟普通的API开发没有什么区别;至此,需求分析完毕,还是比较简单嘛。
第一个版本
根据响应的数据格式,二黄很快设计出核心的数据结构:
- 封装终极响应结果的 Response 类
public class Response
{
int status;
String msg;
String apiName;
ResponseData data;
}
- 封装订单交易数量的ResponseData 类
public class ResponseData
{
private List result; // OrderQuantityDTO 即为订单交易数量DTO
private boolean hasNext;
private long totalSize;
private int pageNum;
}
至于具体的 crud 模板代码,二黄一顿 ctrl c + ctrl v ,顺利撸完,测试居然一把过:
说着,二黄向光头成走读 OrderQuantityController 组件的代码:
- 通过 orderQuantityService组件,从数据库中读取订单交易量数据。
ResponseData responseData = orderQuantityService.selectPage(requestParam);
- 组装 responseDataVO 对象。
将 responseData.List转换成 responseData.List
List orderQtyDTOs = (List)
responseData.getResult();
List orderQtyVOs = new ArrayList<
OrderQuantityVO>(orderQtyDTOs .size());
for (OrderQuantityDTO orderQtyDTO : orderQtyDTOs)
{
OrderQuantityVO orderQuantityVO = new OrderQuantityVO();
orderQuantityVO.setArea(orderQtyDTO.getArea());
orderQuantityVO.setBillingQty(orderQtyDTO.getBilling_qty());
orderQuantityVO.setOrgId(orderQtyDTO.getOrg_id());
orderQuantityVO.setPeriodId(orderQtyDTO.getPeriod_id());
orderQtyVOs.add(orderQuantityVO);
}
从 responseData 中读取并封装其他分页相关字段
ResponseData responseDataVO= new ResponseData<>();
responseDataVO.setHasNext(responseData.isHasNext());
responseDataVO.setPageNum(responseData.getPageNum());
responseDataVO.setTotalSize(responseData.getTotalSize());
responseDataVO.setResult(orderQtyVOs);
- 最后,组装 Response 对象
response.setData(resDataVO);
response.setApiName("orderQty");
response.setMsg("success");
response.setStatus(-1);
整个OrderQuantityController 组件代码大致如下:
@GetMapping("/orderQty")
public Response listByPageRubbish(OrderQuantityQuery requestParam)
{
Response response = new Response<>();
ResponseData responseData =
orderQuantityService.selectPage(requestParam);
List orderQtyDTOs =
(List) responseData.getResult();
List orderQtyVOs =
new ArrayList(responseData.getResult().size());
for (OrderQuantityDTO orderQtyDTO : orderQtyDTOs)
{
OrderQuantityVO orderQuantityVO = new OrderQuantityVO();
orderQuantityVO.setArea(orderQtyDTO.getArea());
orderQuantityVO.setBillingQty(orderQtyDTO.getBilling_qty());
orderQuantityVO.setOrgId(orderQtyDTO.getOrg_id());
orderQuantityVO.setPeriodId(orderQtyDTO.getPeriod_id());
orderQtyVOs.add(orderQuantityVO);
}
ResponseData responseDataVO = new ResponseData<>();
responseDataVO.setHasNext(responseData.isHasNext());
responseDataVO.setPageNum(responseData.getPageNum());
responseDataVO.setTotalSize(responseData.getTotalSize());
responseDataVO.setResult(orderQtyVOs);
response.setData(responseDataVO);
response.setApiName("orderQty");
response.setMsg("success");
response.setStatus(-1);
return response;
}
代码走读到这里,光头成乐了
原来代码洁癖重度症患者光头成眼中屎一样的代码,是指 response 对象的构建,以及responseData
“二黄子啊,狗当有点追求,写代码不能仅仅满足于实现功能,这一手屎一样的代码配不上你的颜值!想想看,怎么优化,让response的构建更简洁,可读性及扩展性更好 ?”
“response 对象的构建还能优化? 总共也就涉及 status等 4 个属性的赋值操作啊?” ,二黄一脸懵逼。
“当对象的属性个数比较多或者某个属性的构建比较麻烦,比如,response.ResponseData
“构造器模式?本黄以前学过,看过一些 demo,但从来没有在真实项目中用过 ... 额,好像有点道理,如果对象的属性有10个的话,可能写着写着就忘了给某个属性赋值了;而 response.ResponseData 等复杂属性的赋值,让调用者 controller 组件 的代码看起来,确实有点臃肿 。”
构造你的复杂对象
滴滴滴滴,系好安全带,飙车的节奏,are you ready,Mr.Huang ?
首先,把待构建的复杂对象的属性,抽成一个个对应的方法,放在接口中, 像 response 对象,我们可以这么做:
public interface ResponseBuilder
{
void status(int value); // 方法名称,对应 Response 属性名称
void msg(String msg);
void apiName(String apiName);
void responseData(ResponseData> data);
Response build(); // 向调用者返回我们构造好的 response 对象
}
然后,在子类具体实现中,封装 response 对象的赋值逻辑:
public class OrderQtyResponseBuilder implements ResponseBuilder
{
private Response response = new Response<>();
@Override
public void status(int value)
{
response.setStatus(value);
}
@Override
public void msg(String msg)
{
response.setMsg(msg);
}
@Override
public void apiName(String apiName)
{
response.setApiName(apiName);
}
@Override
public void responseData(ResponseData> data)
{
List orderQtyDTOs = (List) data.getResult();
List orderQtyVOs =
new ArrayList(data.getResult().size());
for (OrderQuantityDTO orderQtyDTO : orderQtyDTOs)
{
OrderQuantityVO orderQuantityVO = new OrderQuantityVO();
orderQuantityVO.setArea(orderQtyDTO.getArea());
orderQuantityVO.setBillingQty(orderQtyDTO.getBilling_qty());
orderQuantityVO.setOrgId(orderQtyDTO.getOrg_id());
orderQuantityVO.setPeriodId(orderQtyDTO.getPeriod_id());
orderQtyVOs.add(orderQuantityVO);
}
ResponseData resDataVO = new ResponseData<>();
resDataVO.setHasNext(data.isHasNext());
resDataVO.setPageNum(data.getPageNum());
resDataVO.setTotalSize(data.getTotalSize());
resDataVO.setResult(orderQtyVOs);
response.setData(resDataVO);
}
@Override
public Response build()
{
return this.response;
}
}
这样,重构后的controller组件长这样:
@GetMapping("/orderQty")
public Response listByPage(OrderQuantityQuery requestParam)
{
ResponseBuilder responseBuilder = new OrderQtyResponseBuilder();
try
{
responseBuilder .apiName("orderQty");
ResponseData responseData =
orderQuantityService.selectPage(requestParam);
responseBuilder .msg("success");
responseBuilder .status(0);
responseBuilder .responseData(responseData);
return responseBuilder .build(); // 通过 build 接口,获取构造好的response对象
}
catch (Exception e)
{
logger.error("error", e);
responseBuilder .msg("fail");
responseBuilder .status(-1);
return responseBuilder .build();
}
}
怎么样二黄子,代码是不是简洁了很多?复杂属性 responseData 的赋值被封装进responseBuilder 自身中,面向对象的基本设计原则-“封装” 体会到了么?
实际上,构造者模式广泛应用于各类开源框架中,Spark DataSet 编程入口类SparkSession的构造就是一个的例子:
SparkSession spark = SparkSession
.builder()
.appName("Java Spark SQL basic example")
.config("spark.some.config.option", "some-value")
.getOrCreate();
“ 哇,酷酷的感觉,本黄就喜欢这种一点到底的链式风赋值:点点点点就完成了对象的赋值,能省不少笔墨 - 但 Spark是什么?Response 对象的构建好像并不能像上面那样点点点啊!”
其实简单,只需对 ResponseBuilder 接口的返回值稍加改造,即可完成链式风赋值:
public interface ResponseBuilder
{
ResponseBuilder status(int value); // 属性相关方法的返回值由 void , 替换成 ResponseBuilder
ResponseBuilder msg(String msg);
ResponseBuilder apiName(String apiName);
ResponseBuilder responseData(ResponseData> data);
Response build();
}
同步修改实现类中相关方法的返回值:
public class OrderQtyResponseBuilder implements ResponseBuilder
{
private Response response = new Response<>();
@Override
public ResponseBuilder status(int value)
{
response.setStatus(value);
return this; // 添加返回当前ResponseBuilder对象实例
}
@Override
public ResponseBuilder msg(String msg)
{
response.setMsg(msg);
return this; // 添加返回当前ResponseBuilder对象实例
}
// 其他方法同样的操作,略
}
这样,调用者 controller的代码,我们就可以走链式风赋值了:
public Response listByPage(OrderQuantityQuery requestParam)
{
ResponseBuilder resBuilder = new OrderQtyResponseBuilder();
try
{
resBuilder.apiName("orderQty");
ResponseData responseData =
orderQuantityService.selectPage(requestParam);
return resBuilder.responseData(responseData) // 链式风赋值
.msg("success")
.status(0).build();
} catch (Exception e)
{
logger.error("error", e);
return resBuilder.msg("fail")
.status(-1).build();
}
}
二黄呀,至于 spark 是什么,自己查资料:作为java程序媛,对大数据人工智能相关技术一点都不了解,怎么能唬住同事、老板,怎么升级加薪?
“ 等等!光头成,controller 组件的代码是简洁了很多,但 responseData
有!其实,下面这行代码也不够优雅,我们直接 new 出一个 OrderQtyResponseBuilder 实例:
ResponseBuilder resBuilder = new OrderQtyResponseBuilder();
如果哪天想换成其他的实现类,最直接的,比方说,我发现这个 OrderQtyResponseBuilder 类的命名(鬼知道中间那个 Qty 代表什么)有问题,将其改成 OrderQuantityResponseBuilder ,这样我得跑到 OrderQuantityController 及所有引用 OrderQtyResponseBuilder的地方修改,有点 low!
至于上面两个场景怎么优化,需要用到其他神器,以后有机会再教你怎么玩,心急吃不了热豆腐 ,今天你先好好体会与总结构造者模式的基本套路,拿下第一枚神器,,,啊,听说九寨沟新开了一家水煮鱼店,今儿个开心,一起整点?
二黄的画图总结能力还是很不错的,分分钟画出构造器模式的核心套路,
顺手一箱 82年的老青岛,随光头成,奔向九沟寨。
总 结
当对象的属性比较多,或者某个属性的赋值涉及复杂的参数校验、转换等操作时,我们可以应用构造者模式,封装与简化该对象的创建与使用
注】项目github地址:open API 1.0