阿里P8架构师教你kill祖传石山代码重复/大量ifelse( 二 )


若有新用户类型、用户逻辑 , 只要新增一个XXXUserCart类继承AbstractCart , 实现特殊的优惠和运费处理逻辑即可 。
工厂+模板方法模式 , 消除了重复代码 , 还避免修改既有代码 。 这就是设计模式中的开闭原则:对修改关闭 , 对扩展开放 。
3 注解+反射消除重复代码3.1 需求银行提供了一些API接口 , 对参数的序列化不使用JSON , 而需要我们把参数依次拼在一起构成一个大字符串 。

  • 按照银行提供的API文档的顺序 , 把所有参数构成定长的数据 , 然后拼接在一起作为整个字符串
  • 因为每种参数都有固定长度 , 未达到长度时需填充:字符串类型的参数不满长度部分需要以下划线右填充 , 也就是字符串内容靠左数字类型的参数不满长度部分以0左填充 , 也就是实际数字靠右货币类型的表示需要把金额向下舍入2位到分 , 以分为单位 , 作为数字类型同样进行左填充 。对所有参数做MD5操作作为签名(为了方便理解 , Demo中不涉及加盐处理) 。
比如 , 创建用户方法和支付方法的定义是这样的:
阿里P8架构师教你kill祖传石山代码重复/大量ifelse文章插图
阿里P8架构师教你kill祖传石山代码重复/大量ifelse文章插图
3.2 菜鸟实现直接根据接口定义实现填充、加签名、请求调用:
public class BankService {// 创建用户public static String createUser(String name, String identity, String mobile, int age) throws IOException {StringBuilder stringBuilder = new StringBuilder();// 字符串靠左 , 多余的地方填充_stringBuilder.append(String.format("%-10s", name).replace(' ', '_'));stringBuilder.append(String.format("%-18s", identity).replace(' ', '_'));// 数字靠右 , 多余的地方用0填充stringBuilder.append(String.format("%05d", age));// 字符串靠左stringBuilder.append(String.format("%-11s", mobile).replace(' ', '_'));// MD5签名stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString()));return Request.Post("http://localhost:45678/reflection/bank/createUser").bodyString(stringBuilder.toString(), ContentType.APPLICATION_JSON).execute().returnContent().asString();}// 支付public static String pay(long userId, BigDecimal amount) throws IOException {StringBuilder stringBuilder = new StringBuilder();// 数字靠右stringBuilder.append(String.format("%020d", userId));// 金额向下舍入2位到分 , 以分为单位 , 作为数字靠右 , 多余的地方用0填充stringBuilder.append(String.format("%010d", amount.setScale(2, RoundingMode.DOWN).multiply(new BigDecimal("100")).longValue()));// MD5签名stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString()));return Request.Post("http://localhost:45678/reflection/bank/pay").bodyString(stringBuilder.toString(), ContentType.APPLICATION_JSON).execute().returnContent().asString();}}这段代码的重复粒度更细:
  • 三种标准数据类型的处理逻辑有重复
  • 处理流程中字符串拼接、加签和发请求的逻辑 , 在所有方法重复
  • 实际方法的入参的参数类型和顺序 , 不一定和接口要求一致 , 容易出错
  • 代码层面针对每一个参数硬编码 , 无法清晰地进行核对 , 如果参数达到几十个、上百个 , 出错的概率极大 。
3.3 重构秘技之注解 --tt-darkmode-color: #A3A3A3;">针对银行请求的所有逻辑均使用一套代码实现 , 不会出现任何重复 。
要实现接口逻辑和逻辑实现的剥离 , 首先要以POJO类定义所有的接口参数 。
  • 创建用户API的参数
@Datapublic class CreateUserAPI {private String name;private String identity;private String mobile;private int age;}有了接口参数定义 , 就能通过自定义注解为接口和所有参数增加一些元数据 。
  • 如下定义一个接口API的注解BankAPI , 包含接口URL地址和接口说明
再定义一个自定义注解@BankAPIField , 描述接口的每一个字段规范 , 包含参数的次序、类型和长度三个属性:
阿里P8架构师教你kill祖传石山代码重复/大量ifelse文章插图
  • 定义CreateUserAPI类描述创建用户接口的信息 , 通过为接口增加@BankAPI注解 , 来补充接口的URL和描述等元数据;通过为每一个字段增加@BankAPIField注解 , 来补充参数的顺序、类型和长度等元数据:
  • 类似的还有PayAPI类 这2个类继承的AbstractAPI类是一个空实现 , 因为该案例中的接口无公共数据 。
通过这俩类 , 即可在几秒钟内完成和API清单表格的核对 。 若我们的核心翻译过程(即把注解和接口API序列化为请求需要的字符串的过程)没问题 , 只要注解和表格一致 , API请求翻译就不会有问题 。