《设计模式》——迪米特法则

拥有回忆 提交于 2020-03-11 10:07:25

定义

  其实《设计模式之禅》关于原则的部分,我最喜欢的就是《迪米特法则》没什么特殊原因,前段时间部门培训,让我出个培训内容,就是选择的迪米特法则。其实原因很简单,就因为看它名字都不知道究竟是拿来干啥的。
先臭美一下《设计模式》——目录,然后让我们进入正题。
  所以究竟什么是迪米特法则呢?
什么是迪米特法则

  迪米特法则(Law of Demeter,LoD)也称为最少知识原则(Least Knowledge Principle,LKP),虽然名字不同,但描述的是同一个规则:一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没关系,那是你的事情,我就知道你提供的这么多public方法,我就调用这么多,其他的我一概不关心。

  对于这个定义说实话我还是挺喜欢的,但也是生活中我很难做到的一点,那就是“我就调用这么多,其他的我一概不关心”。其实在小的公司,这样的性格是不好的,毕竟很难做到一个萝卜一个坑,大家或多或少都需要承担一些职能外的任务。而且从个人成长的角度来说,其实也是参与越多,收获也会越多。
   有感一段废话,可以跳过不看。职场中有一种情况,那就是A只做十件事,而且十件事都完成的很好;而B需要做一百件事,但是有一件事没有做好。这两个人谁的年终评估会更高一些呢?当然,这里不是劝大家“各人自扫门前雪,莫管他人瓦上霜”(虽然,这是迪米特法则提倡的)。只是有这么几个建议:

  1. 能力范围内,多自己有所成长的,能多参与还是多参与(不要超能力,不要超量)
  2. 既然参与了,能做好就尽己所能做到最好
  3. 如果一旦失败了要用于承担,实事求是说明情况,而不是抱怨于自己比别人多做了多少
  4. 当领导职能看到自己做错的(不是只说你错了,毕竟确实有问题。要看其他相关福利待遇、boss对你的日常态度等方方面面),而不是作对的时,优先考虑一下自己的方法以及态度
  5. 如果如上都没有问题,可以考虑换个舒服点的环境

   当然,上面这么多废话,主要目的就是想说明,当我们的代码不遵守迪米特法则的时候,一旦出现问题,要面临这么多的麻烦事。毕竟你作为主角还能为自己申辩一下,而我们可怜的代码,只能被别人吐槽“这代码真烂”,却无力反抗啊。
而《设计模式之禅》中,迪米特法则对类的低耦合做出了如下四层定义:

  1. 只和朋友交流
  2. 朋友之间也是有距离的
  3. 是自己的就是自己的
  4. 谨慎使用Serializable

只和朋友交流

   迪米特法则还有一个英文解释是:Only talk to your immediate friends(只与直接的朋友通信。)什么叫做直接的朋友呢?每个对象都必然会与其他对象有耦合关系,两个对象之间的耦合就成为朋友关系。

   对于一个吃货来说,这个时候必须要吃可乐鸡翅了!
可乐鸡翅
   为了吃可乐鸡翅的,我们总共有两种实现途径:

  1. 自己做
  2. 去饭店

自己做

   先说说自己做,最基本的,我们需要知道配料,还需要知道制作可乐鸡翅的菜谱。当然,这些都不重要。作为一个懒汉来说,我最怕的是还要去购买食材,买回来以后还要自己制作!

配料
   步骤:

  1. 把鸡翅清洗干净,划花刀。加入蚝油、酱油、盐、料酒、姜,腌制鸡翅。(天气炎热,最好放入冰箱,避免发臭。腌制半个小时左右)
  2. 在锅里加入适量油,把腌制过后的鸡翅放入锅中两面煎黄。
  3. 把刚才腌制的调料一起倒入,加入可乐,大火烧开,然后收汁 !
  4. 健康又卫生的可乐鸡翅就做好了!

   以上可乐鸡翅制作步骤来自下厨房

public class Blog {
    @Test
    public void order() {
        MySelf mySelf = new MySelf();
        mySelf.order(new Waitress());
    }
}

class ColaChickenWings {
    private ChickenWings chickenWings;
    private Cola cola;

    public boolean cooked() {
        return 0 == new Random().nextInt(5);
    }

    public ChickenWings getChickenWings() {
        return chickenWings;
    }

    public void setChickenWings(ChickenWings chickenWings) {
        this.chickenWings = chickenWings;
    }

    public Cola getCola() {
        return cola;
    }

    public void setCola(Cola cola) {
        this.cola = cola;
    }

    /**
     * 鸡翅
     */
    class ChickenWings {

    }

    /**
     * 可乐
     */
    class Cola {

    }
}

class MySelf {
    public void order(Waitress waitress) {
        ColaChickenWings colaChickenWings = new ColaChickenWings();
        //  买鸡翅
        if (null == colaChickenWings.getChickenWings()) {
            colaChickenWings.setChickenWings(colaChickenWings.new ChickenWings());
        }
        //  买可乐
        if (null == colaChickenWings.getCola()) {
            colaChickenWings.setCola(colaChickenWings.new Cola());
        }
        waitress.orderColaChickenWings(colaChickenWings);
    }
}

class Waitress {
    public void orderColaChickenWings(ColaChickenWings colaChickenWings) {
        if (null != colaChickenWings) {
            while (true) {
                if (colaChickenWings.cooked()) {
                    System.out.println("美味的可乐鸡翅出锅喽!!!");
                    break;
                } else {
                    System.out.println("马上就好了");
                }
            }
        }
    }
}

上菜

去饭店

   说到去饭店,这个步骤就复杂了,我们到饭店以后还要高喊一声:
来份可乐鸡翅
   之后只需要等上那么一会,可爱的服务员小姐姐就把可乐鸡翅端到我面前来了!!!惊不惊喜?意不意外?

  显而易见,去饭店这种选择就符合了我们的最小知识原则,而我们只需要与可爱的服务员小姐姐成为朋友,她就会点好菜,再给你端上来就好了,而不需要我们去了解可乐鸡翅那么繁琐的制作过程。

public class Blog {
    @Test
    public void order() {
        MySelf mySelf = new MySelf();
        mySelf.order(new Waitress());
    }
}

class ColaChickenWings {
    private ChickenWings chickenWings;
    private Cola cola;

    public boolean cooked() {
        // 买鸡翅
        if (null == chickenWings) {
            chickenWings = new ChickenWings();
        }
        // 买可乐
        if (null == cola) {
            cola = new Cola();
        }
        return 0 == new Random().nextInt(5);
    }

    /**
     * 鸡翅
     */
    class ChickenWings {

    }

    /**
     * 可乐
     */
    class Cola {

    }
}

class MySelf {
    public void order(Waitress waitress) {
        waitress.orderColaChickenWings();
    }
}

class Waitress {
    public void orderColaChickenWings() {
        ColaChickenWings colaChickenWings = new ColaChickenWings();
        while (true) {
            if (colaChickenWings.cooked()) {
                System.out.println("美味的可乐鸡翅出锅喽!!!");
                break;
            } else {
                System.out.println("马上就好了");
            }
        }
    }
}

上菜
  当然,这里还是需要注意一点,那就是虽然老话说“朋友的朋友是朋友”,但是你找朋友的朋友借个钱试试?

  一个类只和朋友交流,不与陌生类交流,不要出现getA().getB().getC().getD()这种情况(在一种极端的情况下允许出现这种访问,即每一个点号后面的返回类型都相同),类与类之间的关系是建立在类间的,而不是方法间,因此一个方法尽量不引入一个类中不存在的对象,当然,JDK API提供的类除外。

链式调用

朋友之间也是有距离的

  人和人之间是有距离的,太远关系逐渐疏远,最终形同陌路;太近就相互刺伤。对朋友关系描述最贴切的故事就是:两只刺猬取暖,太远取不到暖,太近刺伤了对方,必须保持一个既能取暖又不刺伤对方的距离。迪米特法则就是对这个距离进行描述,即使是朋友类之间也不能无话不说,无所不知。

朋友之间也是有距离的
  还是说我们的可乐鸡翅,当美丽的小姐姐想要吃可乐鸡翅的时候怎么办呢?还是两个情况:

  1. 我们很熟
  2. 我们不熟

我们很熟

  由于我对小姐姐比较熟悉,知道一些小姐姐的要求,在做可乐鸡翅的时候自然需要考虑进去

  • 可乐要0卡的
  • 小姐姐讨厌蒜的味道
  • 小姐姐喜欢吃鲜鸡翅,不能是冻的
  • 小姐姐减肥,不能放太多油
  • 小姐姐……

都需要考虑

public class Blog {
    @Test
    public void order() {
        MySelf mySelf = new MySelf();
        mySelf.cookColaChickenWings(new CuteGirl());
    }
}

class ColaChickenWings {
    private Calorie calorie = new Calorie();
    private Garlic garlic = new Garlic();

    public Calorie getCalorie() {
        return calorie;
    }

    public void setCalorie(Calorie calorie) {
        this.calorie = calorie;
    }

    public Garlic getGarlic() {
        return garlic;
    }

    public void setGarlic(Garlic garlic) {
        this.garlic = garlic;
    }

    class Calorie {

    }

    class Garlic {

    }
}

class MySelf {
    public void cookColaChickenWings(CuteGirl cuteGirl) {
        ColaChickenWings colaChickenWings = new ColaChickenWings();
        if (null != colaChickenWings.getCalorie()) {
            colaChickenWings.setCalorie(null);
        }

        if (null != colaChickenWings.getGarlic()) {
            colaChickenWings.setGarlic(null);
        }

        cuteGirl.eat(colaChickenWings);
    }
}

class CuteGirl {
    public void eat(ColaChickenWings colaChickenWings) {
        if (null != colaChickenWings.getGarlic() || null != colaChickenWings.getCalorie()) {
            System.out.println("不好吃,再也不理你了");
        }
    }
}

我们不熟

  那还管什么,我还不想怎么做怎么做啊!
味道怎么样?

public class Blog {
    @Test
    public void order() {
        MySelf mySelf = new MySelf();
        mySelf.cookColaChickenWings(new CuteGirl());
    }
}

class ColaChickenWings {
    private Calorie calorie = new Calorie();
    private Garlic garlic = new Garlic();

    public Calorie getCalorie() {
        return calorie;
    }

    public void setCalorie(Calorie calorie) {
        this.calorie = calorie;
    }

    public Garlic getGarlic() {
        return garlic;
    }

    public void setGarlic(Garlic garlic) {
        this.garlic = garlic;
    }

    class Calorie {

    }

    class Garlic {

    }
}

class MySelf {
    public void cookColaChickenWings(CuteGirl cuteGirl) {
        cuteGirl.eat(new ColaChickenWings());
    }
}

class CuteGirl {
    public void eat(ColaChickenWings colaChickenWings) {
        if (null != colaChickenWings.getGarlic()) {
            colaChickenWings.setGarlic(null);
        }
        if (null != colaChickenWings.getCalorie()) {
            colaChickenWings.setCalorie(null);
        }
        System.out.println("味道还行");
    }
}

  看看,是不是省心了很多(是不是注孤生不在考虑范围内)

  一个类公开的public属性或非法越多,修改时涉及的面也就越大,变更引起的风险扩散也就越大。因此,为了保持朋友类间的距离,在设计时需要反复衡量:是否还可以再减少public方法和属性,是否可以修改为private、package-private(包类型,在类、方法、变量前不加访问权限,则默认为包类型)、protected等访问权限,是否可以加上final关键字等。
  迪米特法则要求类“羞涩”一点,尽量不要对外公布太多的public方法和非静态的public变量,尽量内敛,多使用private、package-private、protected等访问权限。

是自己的就是自己的

  这一条又说明了什么?可乐鸡翅还是要会做的!毕竟就算饭店倒闭了,就算小姐姐离开了我,至少我还是有可乐鸡翅吃的!
我要吃鸡翅

  在实际应用中经常会出现这样一个方法:放在本类中也可以,放在其他类中也没有错,那怎么去衡量呢?你可以坚持这样一个原则:如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中。

  而从前面的例子也可以看得出来,实际就是尽可能将类自身能够完成的判断、流程等,在该类内部完成,只对外提供一个调取的public即可,而不是将这些都暴露在外,让调用的类自己去判断(比如点菜,和陌生小姐姐吃可乐鸡翅)。

谨慎使用Serializable

  好吧,又冒出来了一个新名词Serializable,它究竟是什么意思呢?

  序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

  好了,下面来看看在《设计模式之禅》中关于这部分的介绍:

  在一个项目中使用RMI(Remote Method Invocation,远程方法调用)方式传递一个VO(Value Object,值对象),这个对象就必须实现Serializable接口(仅仅是一个标志性接口,不需要实现具体的方法),也就是把需要网络传输的对象进行序列化,否则就会出现NotSerializableException异常。突然有一天,客户端的VO修改了一个属性的访问权限,从private变更为public,访问权限扩大了,如果服务器上没有做出相应的变更,就会报序列化失败,就这么简单。

  对于这段话,我个人的理解是:“\color{red}{能序列化就序列化,不序列化可能会挂!}

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!