【Go实现】实践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为例进一步探讨 。对于类(服务注册中心)来说,它对外提供的基本能力有服务注册、更新、去注册和发现功能,那么,我们可以这么实现:
// Registry 服务注册中心type Registry struct {dbdb.Dbserver*http.ServerlocalIpstring}func (r *Registry) register(req *http.Request) *http.Response {...}func (r *Registry) deregister(req *http.Request) *http.Response {...}func (r *Registry) update(req *http.Request) *http.Response {...}func (r *Registry) discovery(req *http.Request) *http.Response {...}...
上述实现中,包含了、、、等4个主要方法,正好对应了对外提供的能力,看起来已经是职责单一了 。
但是在仔细思考一下就会发现,服务注册、更新和去注册是给专门给服务提供者使用的功能,而服务发现则是专门给服务消费者使用的功能 。服务提供者和服务消费者是两类不同的角色,它们产生变化的时间和方向都可能不同 。比如:
当前服务发现功能是这么实现的:从满足查询条件的所有中挑选一个返回给服务消费者(也即自己做了负载均衡) 。
假设现在服务消费者提出新的需求:把所有满足查询条件的都返回,由服务消费者自己来做负载均衡 。
为了实现这样的功能,我们就要修改的代码 。按理,服务注册、更新、去注册等功能并不应该受到影响,但因为它们和服务发现功能都在同一个模块()里,于是被迫也受到影响了,比如可能会代码冲突 。
因此,更好的设计是将、、内聚到一个服务管理模块,则放到另一个服务发现模块,服务注册中心再组合和 。
具体实现如下:
// demo/service/registry/svc_management.gotype svcManagement struct {db db.Db...}func (s *svcManagement) register(req *http.Request) *http.Response {...}func (s *svcManagement) deregister(req *http.Request) *http.Response {...}func (s *svcManagement) update(req *http.Request) *http.Response {...}// demo/service/registry/svc_discovery.gotype svcDiscovery struct {db db.Db}func (s *svcDiscovery) discovery(req *http.Request) *http.Response {...}// demo/service/registry/registry.gotype Registry struct {dbdb.Dbserver*http.ServerlocalIpstringsvcManagement *svcManagement // 组合svcManagementsvcDiscovery*svcDiscovery // 组合svcDiscovery}func (r *Registry) Run() error {...// 调用svcManagement和svcDiscovery完成服务return r.server.Put("/api/v1/service-profile", r.svcManagement.register).Post("/api/v1/service-profile", r.svcManagement.update).Delete("/api/v1/service-profile", r.svcManagement.deregister).Get("/api/v1/service-profile", r.svcDiscovery.discovery).Put("/api/v1/subscription", r.svcManagement.subscribe).Delete("/api/v1/subscription", r.svcManagement.unsubscribe).Start()}