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


1、高层模块不应该依赖低层模块,两者都应该依赖抽象
2、抽象不应该依赖细节,细节应该依赖抽象
在DIP的定义里,出现了高层模块、低层模块、抽象、细节等4个关键字,要弄清楚DIP的含义,理解者4个关键字至关重要 。
(1)高层模块和低层模块
一般地,我们认为高层模块是包含了应用程序核心业务逻辑、策略的模块,是整个应用程序的灵魂所在;低层模块通常是一些基础设施,比如数据库、Web框架等,它们主要为了辅助高层模块完成业务而存在 。
(2)抽象和细节
在前文“OCP:开闭原则”一节中,我们可以知道,抽象就是众多细节中的共同点,抽象就是不断忽略细节的出来的 。
现在再来看DIP的定义,对于第2点我们不难理解,从抽象的定义来看,抽象是不会依赖细节的,否则那就不是抽象了;而细节依赖抽象往往都是成立的 。
理解DIP的关键在于第1点,按照我们正向的思维,高层模块要借助低层模块来完成业务,这必然会导致高层模块依赖低层模块 。但是在软件领域里,我们可以把这个依赖关系倒置过来,这其中的关键就是抽象 。我们可以忽略掉低层模块的细节,抽象出一个稳定的接口,然后让高层模块依赖该接口,同时让低层模块实现该接口,从而实现了依赖关系的倒置:
之所以要把高层模块和底层模块的依赖关系倒置过来,主要是因为作为核心的高层模块不应该受到低层模块变化的影响 。高层模块的变化原因应当只能有一个,那就是来自软件用户的业务变更需求 。
下面,我们通过分布式应用系统demo来介绍DIP的实现 。
对于服务注册中心来说,当有新的服务注册上来时,它需要把服务信息(如服务ID、服务类型等)保存下来,以便在后续的服务发现中能够返回给客户端 。因此,需要一个数据库来辅助它完成业务 。刚好,我们的数据库模块实现了一个内存数据库,于是我们可以这么实现:
// 服务注册中心public class Registry implements Service {...// 直接依赖MemoryDbprivate final MemoryDb db;private final SvcManagement svcManagement;private final SvcDiscovery svcDiscovery;private Registry(...) {...// 初始化MemoryDbthis.db = MemoryDb.instance();this.svcManagement = new SvcManagement(localIp, this.db, sidecarFactory);this.svcDiscovery = new SvcDiscovery(this.db);}...}// 内存数据库public class MemoryDb {private final Map> tables;...// 查询表记录publicOptional query(String tableName, PrimaryKey primaryKey) {Table table = (Table) tableOf(tableName);return table.query(primaryKey);}// 插入表记录publicvoid insert(String tableName, PrimaryKey primaryKey, Record record) {Table table = (Table) tableOf(tableName);table.insert(primaryKey, record);}// 更新表记录publicvoid update(String tableName, PrimaryKey primaryKey, Record record) {Table table = (Table) tableOf(tableName);table.update(primaryKey, record);}// 删除表记录publicvoid delete(String tableName, PrimaryKey primaryKey) {Table table = (Table) tableOf(tableName);table.delete(primaryKey);}...}
按照上面的设计,模块间的依赖关系是依赖于,也即高层模块依赖于低层模块 。这种依赖关系是脆弱的,如果哪天需要把存储服务信息的数据库从改成,那么我们也得改的代码:
// 服务注册中心public class Registry implements Service {...// 改成依赖DiskDbprivate final DiskDb db;...private Registry(...) {...// 初始化DiskDbthis.db = DiskDb.instance();this.svcManagement = new SvcManagement(localIp, this.db, sidecarFactory);this.svcDiscovery = new SvcDiscovery(this.db);}...}
更好的设计应该是把和的依赖关系倒置过来,首先我们需要从细节抽象出一个稳定的接口Db: