【Go实现】实践GoF的23种设计模式:SOLID原则( 八 )


仔细思考一下,就会发现上面的设计不太符合消息队列的领域模型,因为Mq的这个抽象确实应该存在的 。
更好的设计应该是保留Mq抽象接口,让Mq继承自和,这样的分层设计之后,既能满足ISP,又能让实现符合消息队列的领域模型:
具体实现如下:
// Mq 消息队列接口,继承了Consumable和Producible,同时又consume和produce两种行为type Mq interface {ConsumableProducible}type MemoryMqInput struct {topicmq.Topicconsumer mq.Consumable // 只依赖Consumable}type AccessLogSidecar struct {socketnetwork.Socketproducer mq.Producible // 只依赖Producibletopicmq.Topic}
接口隔离可以减少模块间耦合,提升系统稳定性,但是过度地细化和拆分接口,也会导致系统的接口数量的上涨,从而产生更大的维护成本 。接口的粒度需要根据具体的业务场景来定,可以参考单一职责原则,将那些为同一类客户端程序提供服务的接口合并在一起 。
DIP:依赖倒置原则
《Clean 》中介绍OCP时有提过:如果要模块A免于模块B变化的影响,那么就要模块B依赖于模块A 。这句话貌似是矛盾的,模块A需要使用模块B的功能,怎么会让模块B反过来依赖模块A呢?这就是依赖倒置原则(The,DIP)所要解答的问题 。
DIP的定义如下:
High-levelnotfrom low-level . Bothon .noton .( )on .
翻译过来,就是:
1、高层模块不应该依赖低层模块,两者都应该依赖抽象
2、抽象不应该依赖细节,细节应该依赖抽象
在DIP的定义里,出现了高层模块、低层模块、抽象、细节等4个关键字,要弄清楚DIP的含义,理解这4个关键字至关重要 。
(1)高层模块和低层模块
一般地,我们认为高层模块是包含了应用程序核心业务逻辑、策略的模块,是整个应用程序的灵魂所在;低层模块通常是一些基础设施,比如数据库、Web框架等,它们主要为了辅助高层模块完成业务而存在 。
(2)抽象和细节
在前文“OCP:开闭原则”一节中,我们可以知道,抽象就是众多细节中的共同点,抽象就是不断忽略细节的出来的 。
现在再来看DIP的定义,对于第2点我们不难理解,从抽象的定义来看,抽象是不会依赖细节的,否则那就不是抽象了;而细节依赖抽象往往都是成立的 。
理解DIP的关键在于第1点,按照我们正向的思维,高层模块要借助低层模块来完成业务,这必然会导致高层模块依赖低层模块 。但是在软件领域里,我们可以把这个依赖关系倒置过来,这其中的关键就是抽象 。我们可以忽略掉低层模块的细节,抽象出一个稳定的接口,然后让高层模块依赖该接口,同时让低层模块实现该接口,从而实现了依赖关系的倒置:
之所以要把高层模块和底层模块的依赖关系倒置过来,主要是因为作为核心的高层模块不应该受到低层模块变化的影响 。高层模块的变化原因应当只能有一个,那就是来自软件用户的业务变更需求 。
下面,我们通过分布式应用系统demo来介绍DIP的实现 。
对于服务注册中心来说,当有新的服务注册上来时,它需要把服务信息(如服务ID、服务类型等)保存下来,以便在后续的服务发现中能够返回给客户端 。因此,需要一个数据库来辅助它完成业务 。刚好,我们的数据库模块实现了一个内存数据库,于是我们可以这么实现:
type Registry struct {dbdb.MemoryDb // 直接依赖MemoryDbserver*http.ServerlocalIpstringsvcManagement *svcManagementsvcDiscovery*svcDiscovery}
按照上面的设计,模块间的依赖关系是依赖于,也即高层模块依赖于低层模块 。这种依赖关系是脆弱的,如果哪天需要把存储服务信息的数据库从改成,那么我们也得改的代码: