场景
同在互联网打工的小伙伴们肯定都面临这样一种场景:
通用逻辑(被多处调用)我们通常会封装成一个方法,那这个方法入参正常来说都不会少,(在开发规范中,经常会看到一条"方法入参正常不超过3个”。)面对入参超过3个的方法我们正常都会封装成一个bo类(这样扩展方法入参,再也不怕被领导diss了)。
如下图,假设现在有三个逻辑需要去调用这个方法,我们是不是都需要去build入参呀,这也是很多小伙伴会选择的调用方式。
大家都知道,在咱们这个行业最不缺的就是挑刺儿的人,这些人经常也被称为有工匠精神的码农哈哈哈!!!
那么就有工匠精神的码农看不惯红框内的代码的(怎么看都是重复代码),每次调用方法都去build入参,太麻烦了!!!
连调用方法都嫌传参麻烦,难道这就是有工匠精神的码农?
不能理解!!!
不过倒也挺好奇究竟是怎么做到自动构造参数的,所以也就有了本篇文章。
优化
现在我们假设上面提到的那个方法是一个订单价格的方法,其实这是我最近遇到的一个真实业务场景:
这里我们只需要了解影响订单价格的因素有订单明细单价、工艺单价、价格类型、订单明细长宽高,以及对应明细下单数量。面对这么多参数,而且这个方法很多地方用到,每个地方去构建参数确实是比较麻烦。
那怎么去做到自动构建入参呢,接下来就是文章的重点了。
我们首先要思考一个问题,这些参数的源头是哪里呢?
(1)可能是下单时候前端传过来的参数。(controller层用XXXDTO接收)
(2)也可能是修改某个订单明细导致订单价格须重新计算,那这个时候需要从数据库查询该订单其他订单明细重新计算,这个时候我们定义XXXPOJO类接收查询结果。(这个为啥不是entity呢,因为这些参数来自不同的实体类)
这里扯多了,不理解上面场景的小伙伴,可以理解成入参来自不同的类。
2. 参数来自不同的类,而这个方法又得去兼容这多个类。
面对这样的场景我们不禁联想到万能数据线。Type-C接口、苹果接口、安卓接口共同连接着USB接口,USB接口才是连接电源的入口。至于这三种接口怎么和USB连接才可以工作就是线内部要做事情了,外部只要关心不同类型手机对应不同的接口就可以了。
大家仔细品一下上面这个我们再常见不过的例子,我觉得太能类比到我们这个场景了。
(1) Type-C接口、苹果接口、安卓接口 --> 想调用方法的不同类(不同数据来源)
(2) USB接口 --> 方法入参
(3) 电源 --> 方法主体
也就是说我们要让这多个类与入参产生关联才可以无缝调用方法主体。
3. 我们不妨把方法的入参定义成一个接口Adapter形式(接口定义各个我们上面说的决定订单价格因素的获取方法),那么这样不同类(数据来源)就可以去实现这个接口,必定重写方法,这就相当于把构建入参的操作移到dto去做。
上面可能说的太抽象了,所以给大家再上个图,看一下具体结构
光说光看,还不如来个实操理解快呢,早知道会这么想,接下来,上代码!!!
(1)方法入参
/**
* 方法入参
*/
public interface OrderAmountCalAdapter {
/**
* 单价 单位:分
*/
BigDecimal obtainPrice();
/**
* 工艺单价 单位:分
*/
BigDecimal obtainCraftPrice();
/**
* 价格类型 2平方价格 3立方价格
*/
Integer obtainPriceType();
/**
* 长度 单位:毫米 mm
*/
Integer obtainLength();
/**
* 宽度 单位:毫米 mm
*/
Integer obtainWidth();
/**
* 厚度 单位:毫米 mm
*/
Integer obtainHeight();
}
(2)主方法
/**
* 计算方法
*/
public long calculate(OrderAmountCalAdapter adapter, Integer qty) {
if (adapter == null) {
throw new ApiException("计算订单金额时载体对象不能为空");
}
if (adapter.obtainPrice() == null) {
throw new ApiException("计算订单金额时金额不能为空");
}
if (adapter.obtainPriceType() == null) {
throw new ApiException("计算订单金额时价格类型不能为空");
}
if (adapter.obtainLength() == null || adapter.obtainWidth() == null || adapter.obtainHeight() == null) {
throw new ApiException("计算订单金额时商品属性信息不能为空");
}
if (qty == null) {
throw new ApiException("计算订单金额时数量不能为空");
}
return doCalculate(adapter, qty).longValue();
}
(3)不同DTO类(不同数据源)
/**
* 不同DTO类,通过实现OrderAmountCalAdapter接口,重写方法获取DTO属性值,也就是上面说的把构建入参移到具体的DTO类来做
*/
@Data
public class OrderProductProperty implements OrderAmountCalAdapter {
@ApiModelProperty(value = "明细ID")
private Long id;
@ApiModelProperty(value = "明细单价")
private Long price;
@ApiModelProperty(value = "下单数量")
private Integer orderQuantity;
@ApiModelProperty(value = "工艺单价")
private Long craftPrice;
@ApiModelProperty(value = "长度(单位mm)")
private Integer length;
@ApiModelProperty(value = "宽度(单位mm)")
private Integer width;
@ApiModelProperty(value = "高度(单位mm)")
private Integer height;
@ApiModelProperty("价格类型 2平方 3立方")
private Integer priceType;
@Override
public BigDecimal obtainPrice() {
return BigDecimal.valueOf(getPrice() == null ? 0L : getPrice());
}
@Override
public BigDecimal obtainCraftPrice() {
return BigDecimal.valueOf(getCraftPrice() == null ? 0L : getCraftPrice());
}
@Override
public Integer obtainPriceType() {
return getPriceType();
}
@Override
public Integer obtainLength() {
return getLength();
}
@Override
public Integer obtainWidth() {
return getWidth();
}
@Override
public Integer obtainHeight() {
return getHeight();
}
}
这就达到我们上面所说的不用构建方法的入参就可以调用方法了,是不是很精妙哈哈哈。
其实仔细想一想,这不就是适配者设计模式。
适配器模式的主要作⽤就是把原本不兼容的接⼝,通过适配修改做到统⼀。
原本只是想说偷个懒,没想到不知不觉还用上了设计模式,代码b格一下提高了,leader连忙称赞小伙子不错不错!
总结
其实本文说的招数,其实就是适配器模式,相信大家也理解到这么做的好处了。
在业务开发中我们会经常的需要做不同接⼝或者方法的兼容,那么这个时候就要想到适配器模式。
设计模式其实是前辈们开发中总结的一下通用范式,我们要合理的学习每种设计模式适合场景,解决什么问题。也不要因设计模式而在写代码时强行使用。
在面对各种各样的开发场景,我们要多思考,多沉淀,才可以让自己的代码更优雅,更好扩展。
加油打工人!奥利给