一文搞懂迪米特法则

本文探讨了软件设计原则中的高内聚、低耦合概念,强调类的设计应保持职责单一,减少不必要的依赖。通过案例分析,指出过于紧密的依赖会导致复杂性和维护难度增加,而恰当的接口隔离可以提高代码的可测试性和可维护性。此外,还讨论了如何在实际应用中平衡内聚与耦合,以及在序列化类设计中应用迪米特法则的例子。
摘要由CSDN通过智能技术生成

定义

没有直接依赖关系的类不要依赖,即便类有依赖关系也只应该依赖必要的接口

高内聚、低耦合

什么是"高内聚、低耦合"

高内聚、低耦合是一种思想,能够有效提高代码的可复用性、可读性、可维护性,减小代码修改影响的范围

"高内聚"用来指导类的设计,"低耦合"用来指导类之间依赖关系的设计。高内聚有助于低耦合。

什么是高内聚

高内聚就是将相近的功能放到一个类里面,不相近的功能放到不同的类里面,相近的功能往往会被同时修改,放到一个类里面,修改的时候就更集中

什么是低耦合

没有直接依赖关系的类不要依赖,即便有依赖关系也只依赖必要的接口,这样类之间的关系更加简单、清晰,修改一个类时影响的依赖类更少,需要测试的依赖类更少

举例

来看一张类设计的对比图
在这里插入图片描述左边是高内聚、低耦合,右边是低内聚、高耦合

左边这张图:类的职责单一,类之间的依赖关系就简单、清晰,依赖的类的个数也少,修改一个类也只影响一个依赖类,只需要测试一个依赖类即可。高内聚有助于低耦合。
右边这张图:类的功能大而全,类之间的依赖关系就比较复杂,依赖的类的个数也比较多,修改一个类影响多个依赖类,需要测试多个依赖类。低内聚导致了高耦合。
高内聚、低耦合,类之间的依赖关系更加简单、清晰,可读性、可维护性更好

理论解读与实战一

理论前半部分:没有直接依赖关系的类不要依赖

业务背景:

一个简化版的搜索引擎爬虫,NetworkTransport类负责底层网络传输,获取请求内容;HtmlDownloader类负责根据url下载对应的html内容;Document类代表网页文档内容,后续用于解析出各种数据

代码实现一

type NetworkTransport struct{}

type HtmlRequest struct {
	url string
}

func (t *NetworkTransport) Send(req *HtmlRequest) []byte {
	//发送请求获取html数据
	return []byte{}
}

type HtmlDownloader struct {
	networkTransport *NetworkTransport
}

type Html struct {
	data []byte
}

func (d *HtmlDownloader) DownloadHtml(url string) *Html {
	req := &HtmlRequest{
		url: url,
	}
	data := d.networkTransport.Send(req)
	return &Html{
		data: data,
	}
}

type Document struct {
	url  string
	html *Html
}

func NewDocument(url string, downloader *HtmlDownloader) *Document {
	html := downloader.DownloadHtml(url)
	return &Document{
		url:  url,
		html: html,
	}
}

上述代码问题点:

  1. NetworkTransport类我们希望它是一个通用的网络通信类,可以获取各种网络请求的数据,而不是只能html请求的数据,所以这个类和HtmlRequest类没有直接依赖关系,不应该依赖
  2. Document类的New方法调用了HtmlDownloader的DownloadHtml方法,逻辑复杂耗时长,影响代码的可测试性;Document这个类是网页文档类,和HtmlDownloader类没有直接依赖关系,不应该依赖

代码实现二

func (t *NetworkTransport) SendV2(addr string, reqData []byte) []byte {
	//发送请求获取数据
	return []byte{}
}

func (r *HtmlRequest) GetAddr() string {
	//根据url获取地址
	return "1.1.1.1"
}

func (r *HtmlRequest) GetContent() []byte {
	//根据url获取请求内容
	return []byte{}
}

func (d *HtmlDownloader) DownloadHtmlV2(url string) *Html {
	req := &HtmlRequest{
		url: url,
	}
	data := d.networkTransport.SendV2(req.GetAddr(), req.GetContent())
	return &Html{
		data: data,
	}
}

type DocumentFactory struct {
	htmlDownloader *HtmlDownloader
}

func (f *DocumentFactory) Instance(url string) *Document {
	html := f.htmlDownloader.DownloadHtmlV2(url)
	return &Document{
		url:  url,
		html: html,
	}
}

理论解读与实战二

理论后半部分:有依赖关系的类只依赖必要的接口

背景

一个Serialization序列化类,包含序列化、反序列化两个方法,但是有的调用方只用到了序列化,有的调用方只用到了反序列化

代码实现一

type Serialization struct{}

func (s *Serialization) Serialize(obj interface{}) []byte {
	return []byte{}
}

func (s *Serialization) DeSerialize(data []byte) interface{} {
	return nil
}

按照迪米特法则,我们需要将Serialize、DeSerialize两个方法拆到两个类,但是这样会降低代码的内聚性,修改序列化编码的时候,需要同时修改两个类,所以这里套用迪米特法则并不好,那么如何既遵循迪米特法则又能不降低代码的内聚性呢?
可以拆成两个接口,一个Serialize接口,一个DeSerialize接口,只用序列化的调用方只依赖序列化接口,只用反序列的调用方只依赖反序列化接口

那问题又来了,即使只用序列化的调用方依赖Serialization类,这个调用方也只依赖了一个额外的DeSerialize接口,那么其实没有必要拆出两个接口增加代码的复杂度;如果后续Serialization这个类序列化、反序列化的方式增加,那么采用拆分接口的方式更好

迪米特法则的目的就是降低代码的耦合度和减少测试工作量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值