二黄的第一枚神器

背 景

二黄的烦恼

“明天周五,下周二要上线?” 、“有没有搞错!今天搞 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 ,顺利撸完,测试居然一把过:


本地测试,顺利通过
准备 code review

说着,二黄向光头成走读 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 转 responseData 那一段属性值拷贝的代码 。

“二黄子啊,狗当有点追求,写代码不能仅仅满足于实现功能,这一手屎一样的代码配不上你的颜值!想想看,怎么优化,让response的构建更简洁,可读性及扩展性更好 ?”

“response 对象的构建还能优化? 总共也就涉及 status等 4 个属性的赋值操作啊?” ,二黄一脸懵逼。

“当对象的属性个数比较多或者某个属性的构建比较麻烦,比如,response.ResponseData属性的赋值就比较麻烦:涉及到DTO至VO的转换,以及hasNext 等其他属性的赋值,这种场景,我们就可以考虑使用 构造器模式 来重构一下代码 。”

“构造器模式?本黄以前学过,看过一些 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 转 responseData ,那一大块属性值拷贝的代码依然坚挺啊,只是换了一个地方存在而已:由 OrderQuantityController移到了 OrderQtyResponseBuilder中 ,有没有办法优化呢?”

有!其实,下面这行代码也不够优雅,我们直接 new 出一个 OrderQtyResponseBuilder 实例:

ResponseBuilder resBuilder = new OrderQtyResponseBuilder();

如果哪天想换成其他的实现类,最直接的,比方说,我发现这个 OrderQtyResponseBuilder 类的命名(鬼知道中间那个 Qty 代表什么)有问题,将其改成 OrderQuantityResponseBuilder ,这样我得跑到 OrderQuantityController 及所有引用 OrderQtyResponseBuilder的地方修改,有点 low!

至于上面两个场景怎么优化,需要用到其他神器,以后有机会再教你怎么玩,心急吃不了热豆腐 ,今天你先好好体会与总结构造者模式的基本套路,拿下第一枚神器,,,啊,听说九寨沟新开了一家水煮鱼店,今儿个开心,一起整点?

二黄的画图总结能力还是很不错的,分分钟画出构造器模式的核心套路,


构造者模式核心套路

顺手一箱 82年的老青岛,随光头成,奔向九沟寨。


总 结

当对象的属性比较多,或者某个属性的赋值涉及复杂的参数校验、转换等操作时,我们可以应用构造者模式,封装与简化该对象的创建与使用

注】项目github地址:open API 1.0

你可能感兴趣的:(二黄的第一枚神器)