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

业务开发中经常会对接某某第三方服务,因此会经常写一些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 条评论
登录 后参与评论

相关文章

来自专栏张善友的专栏

深入浅出事件流处理NEsper(三)

首先介绍一下NESPER的大体结构,NEsper从内容上分为两块,NEsper的核心NEsper.dll和NEsper.IO.dll。 (1)NEsper的核...

230100
来自专栏Python

python select模块详解

要理解select.select模块其实主要就是要理解它的参数, 以及其三个返回值。 select()方法接收并监控3个通信列表, 第一个是所有的输入的data...

51360
来自专栏猿人谷

内存泄露

1.简介       在计算机科学中,内存泄漏(memory leak)指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理...

23380
来自专栏瓜大三哥

Matlab基本语法7

基本编程技巧 脚本m文件和函数m文件,脚本是一系列命令、语句的简单组合。脚本文件中的变量都是全局变量,程序运行后,这些变量保存在matlab的基本工作空间内,一...

25790
来自专栏Python攻城狮

Redis的安装及基本使用1.Redis2.Redis安装3.redis常见配置4.redis数据操作5.redis发布订阅6.主从双备

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。它支持多种类型的数据结构,如 字符串(strings), ...

7510
来自专栏会跳舞的机器人

java并发编程的艺术笔记第一章——并发编程的挑战

单核处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切...

11130
来自专栏WD学习记录

8-21Android 学习ing

(2)Tomcat服务器默认采用的是ISO8859-1编码得到的参数值,这种编码不支持中文,也会导致乱码

10530
来自专栏一“技”之长

iOS代码运行的磨刀石-预编译指令 原

所谓预编译,就是程序代码在编译之前,开发工具为我们预先做的一些工作。不要小瞧这些指令,没有它们,我们的代码可能寸步难行。

10120
来自专栏北京马哥教育

Python imports指南

来源:Python程序员 ID:pythonbuluo 声明:如果你每天写Python,你会发现这篇文章中没有新东西。 这是专为那些像运维人员等偶尔使用Pyt...

26150
来自专栏我是攻城师

给Java字节码加上”翅膀“的JIT编译器

上面文章在介绍Java的内存模型的时候,提到过由于编译器的优化会导致重排序的问题,其中一个比较重要的点地方就是关于JIT编译器的功能。JIT的英文单词是Just...

12950

扫码关注云+社区

领取腾讯云代金券