专栏首页屈定‘s Blog工作--如何封装第三方服务?

工作--如何封装第三方服务?

业务开发中经常会对接某某第三方服务,因此会经常写一些SDK供服务使用,一种比较好的做法就是使用命令模式封装第三方服务,命令模式对于调用方来说简洁明了,也正是封装最根本的目的,便于调用方使用。

命令模式

定义

命令模式是一种行为型模式,其会把具体的行为封装成一个命令Command,接着指定命令接收者Receiver,最后是在Invoker中执行命令。

其UML图如下:(图片来自命令模式

执行时序图如下:

其中角色信息为

  • Command: 抽象命令类
  • ConcreteCommand: 具体命令类
  • Invoker: 调用者,由使用方调用的执行命令的类。
  • Receiver: 接收者,真正执行命令的地方。通常与Invoker放在一起,逻辑复杂时可以使用这种方式分离依赖。

举个例子,遥控器如果要用命令该怎么实现?

首先分清角色,每一个按钮是一个ConcreteCommand,遥控器本身是InvokerReceiver的角色,也就是负责命令的执行,当按下按钮时Invoker接收到一个ConcreteCommand,然后根据内部逻辑触发相应的红外信号,完成整个链路执行。

优点

命令模式的优点主要如下:

  1. 命令模式对于使用方非常友好,使用方只需要关系命令与执行命令的执行器,而不需要关系具体的执行过程,就像遥控器一样,使用方只需要知道遥控器的位置以及按钮所产生的作用即可。
  2. 命令模式内部更加灵活,因为对外的简单,因此对内就可以很灵活的实现各种逻辑,比如命令的记录,撤销,命令队列等等,这些优化在内部很容易实现,并且不会对外部造成影响。
  3. 命令模式很直观,当实现新功能时,那么需要做的事添加一个命令对象,编写对应的执行逻辑,如果逻辑是一个通用的逻辑,那么只需要添加完命令对象就实现了这个功能。(这一点在接下来的实战中有体现)

广义上来说前后端交互过程也算是一种命令模式,他们的交互是HTTP协议,也就是具体的命令对象,每次在命令中填充不同的参数,服务端会返回对应所需要的内容,而客户端不需要理会服务端是如何处理的,只需要知道自己可以使用哪些命令(请求参数),这样理解是不是更能体会到命令模式的本质。

如何封装?

回到问题本身,如何使用命令模式简化第三方请求?根据上文命令模式的简要讲解,可以发现命令模式与第三方服务的需求很像,第三方给我们提供接口,我们使用接口完成某一个功能,接口就是遥控器按钮,第三方就是遥控器本身。

在第三方请求过程中往往有以下几种角色:

  • Req:请求的参数包装类,命令模式下的Command
  • Client:与使用者交互的类,其主要功能是控制整个与第三方交互的流程,给出使用者所期望的返回信息,往往全局单例。命令模式下的Invoker与Receiver
  • Resp:对应于请求的响应结果包装类,命令模式下执行action()后得到的反馈。
  • HttpClient:负责与第三方交互,当然这里只是类比,第三方提供的WebService等等也都是这个角色。

那么封装的目的是让使用者更方便的使用,那么从使用者的角度来观察,使用者往往所期望的是我该填充哪些参数,我该怎么调用请求,我该怎么得到想要的返回值,转换为代码可以理解为以下两行:

// ....context代表 上下文参数
Req req = new Req(context);
Resp resp = client.execute(req);
// .....业务处理

这就是最终封装想要得到的结果。

说得再好也不如看代码来的直接,笔者最近在对接一个云账户结算平台,因此使用该思路写了一个示例项目,该示例项目更好的体现了到底该怎么封装,欢迎fork研究一下。 https://github.com/mrdear/yunzhanghu-sdk-example

示例项目封装后,对于使用方只需要以下几步即可轻松使用。

/**
  * 初始化调用者
  */
 private YunzhanghuClient client =
     new DefaultYunzhanghuClient("0123456", "sha256",
         "78f9b4fad3481fbce1df0b30eee58577", "123456788765432112345678", new WebUtils());

 @Test
 public void test() {
   // 构造银行卡三要素验证命令
   VerifyBankcardThreeFactorReq req = new VerifyBankcardThreeFactorReq();
   req.setCardNo("");
   req.setIdCard("");
   req.setRealName("");

   // 发送命令,并拿到返回值
   VerifyBankcardThreeFactorResp resp = client.execute(req);

   System.out.println(resp);
 }

其他问题

包结构建议

分包原则就是整个jar的package该如何组织,这里参考alipay-sdk的分法。其中internal包是你不想被外部使用的一些类定义,比如转为此次对接定制的签名类,定制的Http类等等,因为Java没有对应的module作用域,因此放在internal中算是一种约定。

com
└── alipay
    └── api
        ├── domain    // 一些实体类,主要为request与response服务,构成其内部的属性
        ├── internal    // 仅供sdk使用的内部工具,不希望外部使用
        ├── request    // 发送请求信息的包装类
        └── response   // 封装返回信息的包装类
        └── commom.java ....      // 一些对外公共的类

下图是笔者在对接云账户结算平台时所定义的包结构,可以作为参考。

提高扩展灵活性建议

对于灵活性的提升需要使用依赖倒置原则,也就是关键点需要依赖对应的接口。比如在一个封装过程中其HttpClient的实现往往就需要暴露出接口,便于使用方针对连接复用,参数调优等等。

举个例子,笔者在对接第三方平台时会把Http请求以接口形式暴露出去,如下所示:

public interface YunzhanghuWebClient {
  /**
   * 单纯的get请求
   *
   * @return 结果
   */
  String yzh_Get(Map<String, Object> headers, String url, Map<String, Object> queryParam) throws Exception;

  /**
   * 单纯的post请求
   *
   * @return 结果
   */
  String yzh_Post(Map<String, Object> headers, String url, Map<String, Object> formParam) throws Exception;

}

然后再封装一个用于自己业务的WebUtils实现,其中会加入自己的业务逻辑。

public class YzhWebUtils {

  private YunzhanghuWebClient yunzhanghuWebClient;

  public YzhWebUtils(YunzhanghuWebClient yunzhanghuWebClient) {
    this.yunzhanghuWebClient = yunzhanghuWebClient;
  }

  public String doGet(String dealerId, String requestId, String url, Map<String, Object> queryParam) {
    try {
      Map<String, Object> header = new HashMap<>(2);
      header.put("dealer-id", dealerId);
      header.put("request-id", requestId);
      // 调用外部提供的http请求
      return yunzhanghuWebClient.yzh_Get(header, url, queryParam);
    } catch (Exception e) {
      logger.error("yunzhanghu get error, url is {}",url, e);
      return "";
    }
  }
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Angular中引入第三方JS库

    最近写http://www.itoolshub.com/的时候用到了日期时间选择器,Angular本身material2只有日期选择器,也不知道为什么官方不提供...

    屈定
  • 由需求而产生的一款db导出excel的工具

    程序员最大的毛病可能就是懒,因为懒所以做出了许许多多提高自己工作效率的工具. 起因于我是商业开发,既然是商业项目避免不了各种数据统计,虽然公司有专门的数据平台,...

    屈定
  • Spring -- 常用的文件工具类

    Spring几乎是当前Java后端项目必备框架之一,其内部有着大量的工具类,了解并熟练使用这些工具类能够节省不少的时间,本文对Spring中文件操作相关工具类进...

    屈定
  • 【批处理学习笔记】第九课:批处理符号(2)

    四、| 这是一个管道传输命令,意思是将上一命令执行的结果传到下一个命令去处理 例如: dir c:\|find "txt" 以上命令是:查找C:\所有,...

    Angel_Kitty
  • 抓住CoAP协议的“心”

    The Constrained Application Protocol(CoAP)是一种专用的Web传输协议,用于受约束的节点和受约束的(例如,低功率,有损)...

    sanshengshui
  • 直方图操作(四)

    直方图操作(四) 之清零电路 一种简单的方法是在所有数据输出完成之后整体清零,另一种思路是在输出完成的下个时钟清零,清零地址为递增,由B口输入。 本设计采用反相...

    瓜大三哥
  • Java设计模式精讲(一):责任链模式

    这篇分享作为一个系列分享的第一篇,主要和大家一起学习一下java设计模式方面的基础,我们现在的安卓开发主要还是基于java语言,所以掌握java...

    open
  • Java初始化方法:类、容器

    put(METHOD_PREFIX, new ParameterAction() {

    WindWant
  • 在Mac下使用MAMP Pro环境

    以前,我使用Windows作为自己的工作系统,后来,改用Mac作为自己的主要工作系统了。 在Windows下,快速搭建*AMP环境,使用xampp或者WAMP之...

    wangxl
  • 【数据】淘宝6月十大类目销售数据(完整版)

    大数据文摘

扫码关注云+社区

领取腾讯云代金券