设计模式之美学习笔记之迪米特法则

这边文章主要来源极客时间的设计模式之美,非常棒的一个教程,大家一定要买这个课程!一定要!

什么是高内聚

所谓高内聚,就是指相近的功能应该放到同一个类中,不相近的功能不要放到同一个类中。 相近的功能往往会被同时修改,放到同一个类中,修改会比较集中,代码容易维护。

什么是松耦合

所谓松耦合是说,在代码中,类与类之间的依赖关系简单清晰。即使两个类有依赖关系,一
个类的代码改动不会或者很少导致依赖类的代码改动。

内聚和耦合的关系

“高内聚”有助于“松耦合”,同理,“低内聚”也会导致“紧耦合”

什么是迪米特法则?

Each unit should have only limited knowledge about other units: only units “closely” related to the current unit. Or: Each unit should only talk to its friends; Don’t talk to strangers.
每个模块(unit)只应该了解那些与它关系密切的模块(units: only units “closely” related to the current unit)的有限知识(knowledge)。或者说,每个模块只和自己 的朋友“说话”(talk),不和陌生人“说话”(talk)。

不该有直接依赖关系的类之间,不要有依赖

简单的爬虫代码

NetworkTransporter:负责底层网络通信,根据请求获取数据
HtmlDownloader:用来通过 URL 获取网页
Document:表示网页文档,后续的网页内容抽取、分词、索引都是 以此为处理对象

    public class NetworkTransporter {
        // 省略属性和其他方法...
        public Byte[] send(HtmlRequest htmlRequest) {
        }
    }
    public class HtmlDownloader {
        private NetworkTransporter transporter;// 通过构造函数或 IOC 注入

        public Html downloadHtml(String url) {
            Byte[] rawHtml = transporter.send(new HtmlRequest(url));
            return new Html(rawHtml);
        }
    }
    public class Document {
        private Html html;
        private String url;

        public Document(String url) {
            this.url = url;
            HtmlDownloader downloader = new HtmlDownloader();
            this.html = downloader.downloadHtml(url);
        }
    }

代码中的问题?

NetworkTransporter的问题?

  作为一个底层网络通信类,我们希望它的功能 尽可能通用,而不只是服务于下载 HTML,所以,我们不应该直接依赖太具体的发送对象 HtmlRequest。从这一点上讲,NetworkTransporter 类的设计违背迪米特法则,依赖了 不该有直接依赖关系的 HtmlRequest 类。
  我们应该如何进行重构,让 NetworkTransporter 类满足迪米特法则呢?我这里有个形象 的比喻。假如你现在要去商店买东西,你肯定不会直接把钱包给收银员,让收银员自己从里面拿钱,而是你从钱包里把钱拿出来交给收银员。这里的 HtmlRequest 对象就相当于钱 包,HtmlRequest 里的 address 和 content 对象就相当于钱。我们应该把 address 和 content 交给 NetworkTransporter,而非是直接把 HtmlRequest 交给NetworkTransporter。

 send(String address, Byte[] data)

HtmlDownloader

这个类没什么问题,只需要把入参修改一下

Document

这个类的问题比较多,主要有三点。
第一,构造函数中 的 downloader.downloadHtml() 逻辑复杂,耗时长,不应该放到构造函数中,会影响代 码的可测试性。代码的可测试性我们后面会讲到,这里你先知道有这回事就可以了。
第二, HtmlDownloader 对象在构造函数中通过 new 来创建,违反了基于接口而非实现编程的 设计思想,也会影响到代码的可测试性。
第三,从业务含义上来讲,Document 网页文档 没必要依赖 HtmlDownloader 类,违背了迪米特法则。

public class Document {
    private Html html;
    private String url;

    public Document(String url, Html html) {
        this.html = html;
        this.url = url;
    }
}
 //工厂对象
public class DocumentFactory {
    //
    private HtmlDownloader downloader;
    //
    public DocumentFactory(HtmlDownloader downloader) {
        this.downloader = downloader;
    }
    //
    public Document createDocument(String url) {
        Html html = downloader.downloadHtml(url);
        return new Document(url, html);
    }
}

有依赖关系的类之间,尽量只依赖必要的接口

public class Serialization {
    public String serialize(Object object) {
        String serializedResult = ...;
        //...
        return serializedResult;
    }

    public Object deserialize(String str) {
        Object deserializedResult = ...;
        //...
        return deserializedResult;
    }
}

 &emsp单看这个类的设计,没有一点问题。不过,如果我们把它放到一定的应用场景里,那就还有继续优化的空间。假设在我们的项目中,有些类只用到了序列化操作,而另一些类只用到反序列化操作。那基于迪米特法则后半部分“有依赖关系的类之间,尽量只依赖必要的接口”,只用到序列化操作的那部分类不应该依赖反序列化接口。同理,只用到反序列化操作的那部分类不应该依赖序列化接口。
 &emsp根据这个思路,我们应该将 Serialization 类拆分为两个更小粒度的类,一个只负责序列化 (Serializer 类),一个只负责反序列化(Deserializer 类)。拆分之后,使用序列化操作 的类只需要依赖 Serializer 类,使用反序列化操作的类只需要依赖 Deserializer 类。拆分 之后的代码如下所示:

public class Serializer{
    public String serialize(Object object) {
        String serializedResult = ...;
        //...
        return serializedResult;
    }
}
public class Deserializer{
   public Object deserialize(String str) {
        Object deserializedResult = ...;
        //...
        return deserializedResult;
    }
}

 &emsp不知道你有没有看出来,尽管拆分之后的代码更能满足迪米特法则,但却违背了高内聚的设 计思想。高内聚要求相近的功能要放到同一个类中,这样可以方便功能修改的时候,修改的 地方不至于过于分散。对于刚刚这个例子来说,如果我们修改了序列化的实现方式,比如从 JSON 换成了 XML,那反序列化的实现逻辑也需要一并修改。在未拆分的情况下,我们只需要修改一个类即可。在拆分之后,我们需要修改两个类。显然,这种设计思路的代码改动范围变大了。
 &emsp如果我们既不想违背高内聚的设计思想,也不想违背迪米特法则,那我们该如何解决这个问 题呢?实际上,通过引入两个接口就能轻松解决这个问题,具体的代码如下所示。实际上, 我们在第 18 节课中讲到“接口隔离原则”的时候,第三个例子就使用了类似的实现思 路,你可以结合着一块儿来看。


重构
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,717评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,501评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,311评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,417评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,500评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,538评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,557评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,310评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,759评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,065评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,233评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,909评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,548评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,172评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,420评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,103评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,098评论 2 352

推荐阅读更多精彩内容