当前位置: 首页 > 科技观察

如何利用迪米特定律实现“高内聚、低耦合”?

时间:2023-03-14 17:41:00 科技观察

1。什么是“高内聚低耦合”?“高内聚低耦合”可以有效提高代码的可读性和可维护性,减少功能变更带来的代码变更范围。很多设计原则也是为了实现代码的“高内聚低耦合”。例如,单一职责原则是面向接口的,而不是面向实现的。编程“高内聚低耦合”是一种通用的设计思想,可以指导:不同的系统、模块、类、甚至函数等粒度代码的设计和开发微服务、框架、组件、类库等不同的开发场景,etc.本文主要以“类”作为本设计思想的应用对象。“高内聚”,引导类本身的设计是“低耦合”的,引导类与类之间的依赖关系设计不是完全独立的,高内聚促成低耦合,而低耦合需要高凝聚力。1.1高内聚的相似功能应该放在同一个类中,不相似的功能应该放在同一个类中。相似的功能往往同时被修改。如果放在同一个类中,修改会比较集中,代码也容易维护。单一职责原则是实现高代码内聚的非常有效的设计原则。1.2低耦合在代码中,类之间的依赖关系简单明了。即使两个类具有依赖关系,一个类中的代码更改也会导致依赖类中的代码更改很少或根本没有更改。依赖注入、接口隔离、面向接口编程和迪米特定律都是为了实现低耦合。1.3“内聚”与“耦合”的关系左边的代码结构是“高内聚,低耦合”;都比较简单。相似的功能归为一类,不相似的功能归为多类。这个类更独立,代码内聚性更好。由于职责单一,每个类依赖的类较少,代码耦合度低。一个类的修改只会影响一个依赖类的代码变化。你只需要测试这个依赖类是否还能正常工作。右边的代码设计:类粒度比较大,内聚性低,功能大而全,不同的功能放在一个类中。导致许多其他类依赖于该类。当修改该类的一个函数代码时,会影响到依赖它的多个类。我们需要测试这三个依赖类是否还能正常工作,“一根毛发牵一发而动全身”。2.LawofDemeter,LOD,得墨忒耳的法则,光看名字是看不出来的。它还有一个名字:最少知识原则。最少知识原则:每个单元对其他单元的了解应该是有限的:只有与当前单元“密切”相关的单元。或者:每个单元应该只和它的朋友交谈;不要和陌生人说话。每个模块only它应该了解那些模块的有限知识(单元:只有与当前单元“密切”相关的单元)。也就是说,每个模块只和自己的朋友“交谈”,不和陌生人“交谈”(talk)。结合实际,将定义描述中的“模块”换成“类”:类之间不应该有直接的依赖关系,有依赖关系的类之间不存在依赖关系,尽量只依赖必要的接口(“知识有限”),因此,迪米特定律实际上由两部分组成,下面将分两种情况进行解读。3.案例3.1不应该有直接依赖的类之间,不应该依赖简化搜索引擎爬取网页,包括以下主要类:NetworkTransporter,负责底层网络通信,根据请求获取数据:HtmlDownloader,通过URLs获取网页:Document,即网页文档,后续的网页内容抽取、分词、索引都是基于这个:如何重构?这段代码虽然“能用”,但并不“好用”。NetworkTransporter作为底层网络通信类,应该尽可能通用,而不是只下载HTML。因此,不要直接依赖过于具体的发送对象HtmlRequest。它的设计违反了迪米特定律,依赖于不应直接依赖的HtmlRequest类。如果你现在要买东西,你肯定不会直接把钱包给收银员让收银员拿钱,而是你把钱包里的钱拿出来给收银员。HtmlRequest对象相当于钱包,HtmlRequest中的地址和内容对象相当于钱。地址和内容应该交给NetworkTransporter,而不是HtmlRequest直接交给NetworkTransporter:文件问题:构造函数中的downloader.downloadHtml()逻辑复杂耗时。它不应该放在构造函数中,这会影响代码的可测试性。HtmlDownloader对象是在构造函数中通过new创建的,违反了面向接口的编程,也影响了代码的可测试性。在业务上,Document网页文档不需要依赖HtmlDownloader类。虽然违背迪米特定律的问题很多,但修改起来很简单:3.2类之间存在依赖关系,尽量只依赖必要的接口。Serialization类负责对象的序列化和反序列化:看类的设计就知道了,没问题。但是如果放到具体的应用场景中,假设项目中有的类只使用了序列化操作,而有的只使用了反序列化。然后在有依赖的类的基础上,尽量只依赖必要的接口:只使用序列化操作的类不应该依赖反序列化接口。只使用反序列化操作的类不应该依赖序列化根据这个,Serialization类应该拆分成两个更小粒度的类,分别只负责序列化(Serializer类)和只负责反序列化(Deserializer类),虽然splitcode可以更好的满足dimispeciallaw,但是违背了高内聚。高内聚要求相似的功能放在同一个类中,这样在功能修改的时候,修改的地方不会太分散。对于这种情况,如果业务需要修改序列化的实现方式,从JSON改为XML,那么反序列化的实现逻辑也要一起改动。拆分前只需要修改一个类,拆分后需要修改两个类!你不想违反高内聚,你也不想违反迪米特定律。将包含序列化和反序列化的Serialization实现类传递给Demo1的构造函数,但依赖的Serializable接口只包含序列化操作。Demo1无法使用Serialization类中的反序列化接口,对反序列化操作没有感知。“取决于有限接口”符合迪米特定律。也体现了“面向接口编程”,结合迪米特定律,可以得出结论:“基于最小接口的编程,而不是最大实现”。多想想这种情况下的重构方案。整个类只包括序列化和反序列化两个操作。只使用序列化操作的用户,即使能感知到唯一的反序列化方法,也没有问题。大的。为了满足迪米特定律而将一个简单的类拆分成两个接口是否过度设计?设计原则本身没有对错,只有能不能用。不要以使用设计原理为目的,具体问题具体分析。Serialization类只包含两个操作,实在没必要??拆分成两个接口。但是,如果在Serialization类中增加更多的功能,实现更多更好的序列化和反序列化方法,重新考虑这个问题:在这种场景下,第二种设计更好。因为本案例的应用场景,大部分代码只用到序列化功能,这些用户不需要了解反序列化,而修改后的Serialization类,反序列化的“知识”从一种方法变成了三种。一旦任何反序列化操作发生代码变更,需要检查和测试所有依赖Serialization类的代码是否还能正常工作。为了减少耦合和测试工作量,反序列化和序列化的功能应该按照Dimiter定律进行隔离。4.总结4.1高内聚低耦合可以有效提高代码的可读性和可维护性,减少功能变化引起的代码变化范围:高内聚,引导类的设计本身就意味着类似的功能应该放在同一个类中,不相似的功能不应该放在同一个类中。相似的功能往往被同时修改,放在同一个类中。修改会更加集中和低耦合,指导类间依赖关系的设计。在代码中,类之间的依赖关系简单明了。即使两个类存在依赖关系,一个类中的代码更改也不会或很少导致依赖类中的代码更改。4.2迪米特法则具有直接依赖关系的类之间不应存在依赖关系;在有依赖关系的类之间,尽量只依赖必要的接口。迪米特定律希望降低类之间的耦合度,让类之间尽可能独立。每个类都应该对系统的其他部分知之甚少。一旦发生变化,更少的类需要知道变化。参考文献:[1]。《重构》[2]。《代码设计之丑》