如何优雅的实现一个Client
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何优雅的实现一个Client相关的知识,希望对你有一定的参考价值。
参考技术A原文首发于InfoQ: 如何优雅的实现一个 Client
创建Client的主要目的是方便与Server进行交互,进而操作Server的数据或资源。Client可以采用不同的协议和Server进行交互,这完全取决于Server支持哪些协议,比如TCP、UDP、HTTP(S)、WebSocket、gRPC等等。使用不同协议的Client实现复杂度和技巧不同,要解决的核心问题也有所不同。本文无法也没有能力针对使用不同协议的Client提供一些有益的指导,为了下文更加清晰的描述,本文限定Client使用HTTP(S)协议与Server交互,Client调用Server开放的标准RESTful API操作Server的资源(创建、删除、更新、查询)。本文所有的样例均使用Java语言编写,不同语言在语法层面有所不同,解决同一问题的思路也不不一样,但是解决问题的思想是一致的,读者可以使用自己擅长的语言实现。
设计一个优雅的Client应当站在使用者的角度,帮助使用者解决痛点,把困难留给自己,便利留给使用者,遵循以下原则有助于设计一个优雅的Client:
Client设计.png Client设计.png为了方便编写样例代码以阐述Client的设计思路,本文以构建 Gotify 的Client为例进行说明。 Gotify 是一个开源的用于发送和接受消息的Server,有兴趣的读者可以去官网查阅相关文档并亲自体验。
按照Client的设计原则,我们要做的第一件事情就是将Client 必要 的配置以接口的形式开放给Client的使用者,因此我们思考的第一件事情是 Client有哪些必要的配置需要开放给使用者 ,Client需要开放的配置与具体的Server相关,对于Gotify来说,最基本的配置包括:Gotify的监听地址,Gotify的监听端口,Gotify是否开启ssl,如果开启ssl还需要配置相应的证书,总结下来Client只需要开放四个必要配置。为了简单又不失一般性,在文中仅开放三个必要配置:Gotify的监听地址,Gotify的监听端口,访问Gotify使用的协议(HTTP/HTTPS),下面使用Java的Interface定义Client需要开放的配置:
Client开放的配置已经定义完成,使用者可以根据自己Gotify Server的情况实现该接口,但是要求使用者提供一个完整的实现,这对于使用者来说非常的不方便。我们需要思考解决 使用者如何简单方便的创建Client配置 ,下面我们从简单易用性出发,考虑提供几个方便使用者创建配置的工具。
Client一共三个配置参数,其中两个必选参数,一个可选参数,参数的数量不是很多,因此我们可以考虑提供两个工厂方法(一个包含三个参数,一个包含两个参数),参数的位置按照scheme:host:port的位置进行排列(每个细节都以使用者为中心设计),因为这和三个参数在标准Uri中的顺序一致,方便使用者记忆。
方法1:拥有三个参数的工厂方法
方法2:拥有两个参数的工厂方法
我们已经定义创建Client配置的工厂方法,下一个需要我们思考的问题是:这些工厂方法应该被组织到哪个类中?在本例子中我们将工厂方法组织到 GotifyClientConfig 接口中,以下两点支撑我们做出这个决定:1、目前为止我们开放给使用者的只有 GotifyClientConfig 接口,如果将方法组织到新的类中,比如我们创建一个工厂类 GotifyClientConfigFactory ,那么这会增加使用的负担,每增加一个开放的类,使用者的学习负担都在增加;2、Java 8 之后的版本从语言层面支持我们这么做,interface提供一些工厂方法在很多场景下都很适用。因此我们将工厂方法组织到 GotifyClientConfig 接口中,修改后的 GotifyClientConfig 接口如下:
工厂方法可以很好的解决在Client中遇到的关于开放配置的问题,但是使用工厂方法有一点限制。工厂方法仅适用于配置参数不多,比如1-4个参数。复杂Client的配置的参数往往非常的多,超过10个参数都是很正常的,在这种多配置参数情况下使用工厂方法就非常的不方便,使用起来甚至比提供一个完整的实现难度还要大,一个拥有超多参数的方法是难以被正确使用的。对于这种多参数场景,设计模式中的Builder模式(建造者模式)是解决这类问题的不二法宝。下面我以常规的写提供一个Builder来简化配置的创建工作。
GotifyClientConfig 的设计、开发工作已经完成,使用者可以使用下面的两种方式根据Gotify Server的实际情况创建 GotifyClientConfig 实例。
接下来让我们一起设计Gotify Client。
上文提到Client的主要功能是与Server进行交互以操作Server的数据/资源,那么设计Client之前,需要掌握Server开放了多少个操作数据的接口,接口如何使用,接口有没有分类等信息。Gotify Server开放的是标准RESTful API,接口的使用非常的方便,可以通过命令行工具、HTTP 客户端等。使用接口不是难点,因此我们要分析的重点是Gotify提供了多少API,API是否有分类,如何在Client中优雅组织实现并开放API。
Gotify一共开放7大类31个API,7大类API分别操作application、message、client、user、health、plugin、version资源。如何组织31个API是需要设计的第一点,按照是否将所有的API通过一个接口开放,API的组织形式可以分为两类:
两种组织API的方式孰优孰劣?其实,不管哪种方案都无法全面优于另一种方案,每个方案都有自己适用的场景,API的数量是选择何种组织方式的主要考虑因素。API的数量比较少选择集中式,这样使用者的学习使用成本都比较低,Client本身的实现复杂度也会降低。如果API的数量较多,而且Server已经按照资源将API分类,使用分类式的方式组织API就更加顺理成章,做出这种选择主要是因为以下两点:
选择使用分类式的方式组织API,那么如何设计这些分类的Client呢?在设计之前我们需要回答以下问题:
弄清楚上面的问题,Client的设计、实现方案也就确定了。
问题分析清楚以后Client的设计方案也就浮出水面,我们设计的Client将具备这样的特点:无状态且线程安全,使用工厂方法创建单例Client,所有的Client都实现了同一个接口用于表明这些Client归属一类。Client的逻辑示意图如下:
Client之间的关系1.png Client之间的关系1.png所有Client的共同行因为Client不同而有所不同,在本文中所有的Client的共同行为是都支持关闭。在Java中可以使用接口和抽象类定义允许有多个实现的类型,使用接口是比抽象类更佳的优秀实践。如果只是想定义一个类型,那么标记接口(不包含任何方法的接口)是一种不错的选择。
AppClient:
MessageClient:
操作资源的Client已经定义完整,但是先不要着急去实现这些Client,下一步我们要解决的问题是如何方便的创建这些Client。
按照上文对方案的介绍, GotifyClient 将负责创建 AppClient 、 MessageClient 等操作资源的Client,下面是GotifyClient的定义:
所有操作资源的Client,比如 AppClient 都将由 GotifyClient 负责创建。对外的接口已经定义清楚,下面来分别实现 AppClient 、 MessageClient 和 GotifyClien t, 注意这些Client的实现都不对使用者开放 。
AppClient 和 MessageClient 的实现需要满足我们对Client的设计要求:Client无状态,Client线程安全。
AppClient 的实现
MessageClient 的实现
GotifyClient 的实现需要满足我们对Client的设计要求:Client是单例,Client的创建方式是统一的。
我们参考 GotifyClientConfig 的实现方式,在 GotifyClient 接口中添加一个静态方法,修改后的 GotifyClient 接口如下:
根据上面设计,我们使用 GotifyClient 过程可以分为四步:
下面以获取运行在本地监听6875端口的Gotify 所有Application为例,详细讲解如何使用我们上面设计的Client。
实现一个Client一定要站在使用者的角度,以使用者为中心,对使用者屏蔽实现的细节和复杂性,将困难留给自己。总结起来实现一个优雅的Client的需要着重提高Client的封装性和易用性。一个优雅的Client不仅使用者使用起来优雅,实现者也要能够优雅的实现,优雅的修改,这就要求我们的Client一定要很好的封装,仅开放必须开放的接口,内部实现尽量不要暴露给使用者。Client的目的就是为了帮助使用者更好的和Server进行交互,因此易用性更加重要,易用性高的Client才能得到使用者的认可,简单易用的Client也可以降低使用出错的概率。
1、样例代码仓库: https://github.com/ctlove0523/gotify-java-client
2、Gotify项目地址: https://github.com/gotify
以上是关于如何优雅的实现一个Client的主要内容,如果未能解决你的问题,请参考以下文章