实践GoF的23种设计模式:SOLID原则( 二 )


SRP:单一职责原则
单一职责原则(The,SRP)应该是SOLID原则中,最容易被理解的一个,但同时也是最容易被误解的一个 。很多人会把“将大函数重构成一个个职责单一的小函数”这一重构手法等价为SRP,这是不对的,小函数固然体现了职责单一,但这并不是SRP 。
SRP传播最广的定义应该是Uncle Bob给出的:
Ahave one, and only one,to .
也即,一个模块应该有且只有一个导致其变化的原因 。
这个解释里有2个需要理解的地方:
(1)如何定义一个模块
我们通常会把一个源文件定义为最小粒度的模块 。
(2)如何找到这个原因
一个软件的变化往往是为了满足某个用户的需求,那么这个用户就是导致变化的原因 。但是,一个模块的用户/客户端程序往往不只一个,比如Java中的类,它可能会被成千上万的程序使用,但我们不能说职责不单一 。因此,我们应该把“一个用户”改为“一类角色”,比如的客户端程序都可以归类为“需要链表/数组功能”的角色 。
于是,Uncle Bob给出了SRP的另一个解释:
Abeto one, and only one, actor.
有了这个解释,我们就可以理解函数职责单一并不等同于SRP,比如在一个模块有A和B两个函数,它们都是职责单一的,但是函数A的使用者是A类用户,函数B的使用者是B类用户,而且A类用户和B类用户变化的原因都是不一样的,那么这个模块就不满足SRP了 。
下面,以我们的分布式应用系统demo为例进一步探讨 。对于类(服务注册中心)来说,它对外提供的基本能力有服务注册、更新、去注册和发现功能,那么,我们可以这么实现:
// demo/src/main/java/com/yrunz/designpattern/service/Registry.javapublic class Registry implements Service {private final HttpServer httpServer;private final Db db;...@Overridepublic void run() {httpServer.put("/api/v1/service-profile", this::register).post("/api/v1/service-profile", this::update).delete("/api/v1/service-profile", this::deregister).get("/api/v1/service-profile", this::discovery).start();}// 服务注册private HttpResp register(HttpReq req) {...}// 服务更新private HttpResp update(HttpReq req) {...}// 服务去注册private HttpResp deregister(HttpReq req) {...}// 服务发现private HttpResp discovery(HttpReq req) {...}}
上述实现中,包含了、、、等4个主要方法,正好对应了对外提供的能力,看起来已经是职责单一了 。
但是在仔细思考一下就会发现,服务注册、更新和去注册是给专门给服务提供者使用的功能,而服务发现则是专门给服务消费者使用的功能 。服务提供者和服务消费者是两类不同的角色,它们产生变化的时间和方向都可能不同 。比如:
当前服务发现功能是这么实现的:从满足查询条件的所有中挑选一个返回给服务消费者(也即自己做了负载均衡) 。
假设现在服务消费者提出新的需求:把所有满足查询条件的都返回,由服务消费者自己来做负载均衡 。
为了实现这样的功能,我们就要修改的代码 。按理,服务注册、更新、去注册等功能并不应该受到影响,但因为它们和服务发现功能都在同一个模块()里,于是被迫也受到影响了,比如可能会代码冲突 。
因此,更好的设计是将、、内聚到一个服务管理模块,则放到另一个服务发现模块,服务注册中心再组合和 。
具体实现如下:
// demo/src/main/java/com/yrunz/designpattern/service/SvcManagement.javaclass SvcManagement {private final Db db;...// 服务注册HttpResp register(HttpReq req) {...}// 服务更新HttpResp update(HttpReq req) {...}// 服务去注册HttpResp deregister(HttpReq req) {...}}// demo/src/main/java/com/yrunz/designpattern/service/SvcDiscovery.javaclass SvcDiscovery {private final Db db;...// 服务发现HttpResp discovery(HttpReq req) {...}}// demo/src/main/java/com/yrunz/designpattern/service/Registry.javapublic class Registry implements Service {private final HttpServer httpServer;private final SvcManagement svcManagement;private final SvcDiscovery svcDiscovery;...@Overridepublic void run() {// 使用子模块的方法完成具体业务httpServer.put("/api/v1/service-profile", svcManagement::register).post("/api/v1/service-profile", svcManagement::update).delete("/api/v1/service-profile", svcManagement::deregister).get("/api/v1/service-profile", svcDiscovery::discovery).start();}}