【OO设计原则】——06迪米特法则

迪米特法则(Law of Demeter LoD)

Least Knowledge Principle

对其他对象有尽可能少的了了解

迪米特法则最初是用来作为面向对象的系统设计风格的一种法则,与1987年秋天由Ian Holland在美国东北大学为一个叫做迪米特(Demeter)的项目设计提出的,因此叫做迪米特法则[LIEB89][LIEB86].这条法则实际上是很多著名系统,比如火星登陆软件系统,木星的欧罗巴卫星轨道飞船的软件系统的指导设计原则.

没有任何一个其他的OO设计原则象迪米特法则这样有如此之多的表述方式,如下几种:

(1)只与你直接的朋友们通信(Only talk to your immediate friends)

(2)不要跟"陌生人"说话(Don't talk to strangers)

(3)每一个软件单位对其他的单位都只有最少的知识,而且局限于那些本单位密切相关的软件单位.

迪米特法则要求我们尽量减少类之间的直接交互,如果两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果一个对象的某一个方法需要调用另一个对象的诸多方法,可以通过第三者转发这个调用,调用者对被调用者内部的情况知道的越少越好。

例01

小明在app上点外卖,他选择了一家披萨店,并向披萨店下了一份订单,披萨店接收了订单并开始为小明准备披萨,类图设计如下:

public class PizzaStore {

    public void takeOrder() {
        System.out.println("披萨店接收订单");
    }

    public void prepareSource() {
        System.out.println("披萨店准备披萨制作原料");
    }

    public void cookPizza() {
        System.out.println("披萨店烹制披萨");
    }

    public void packPizza() {
        System.out.println("披萨店打包披萨");
    }

    public void deliveryPizza() {
        System.out.println("披萨店配送披萨");
    }
}
public class Customer {

    public void orderPizza(PizzaStore pizzaStore){
        pizzaStore.takeOrder();
        pizzaStore.prepareSource();
        pizzaStore.cookPizza();
        pizzaStore.packPizza();
        pizzaStore.deliveryPizza();
    }

    public static void main(String[] args){
        Customer xiaoming = new Customer();
        PizzaStore pizzaStore = new PizzaStore();
        xiaoming.orderPizza(pizzaStore);
    }
}

可以看到小明发起订单后调用披萨店的一个整个过程,包括接收订单、准备原材料、制作披萨、打包、配送等,这样写代码貌似没有问题,但是对于小明来说这些过程其实他根本就不需要关心,小明跟披萨店的交互其实很简单,就是我给你订单,你给我披萨就可以,可是现在小明知道了过多的披萨店的方法,这个就是违反迪米特法则的例子,我们按照迪米特法则进行修改,设计如下:

public class PizzaStore {

    private void takeOrder() {
        System.out.println("披萨店接收订单");
    }

    private void prepareSource() {
        System.out.println("披萨店准备披萨制作原料");
    }

    private void cookPizza() {
        System.out.println("披萨店烹制披萨");
    }

    private void packPizza() {
        System.out.println("披萨店打包披萨");
    }

    private void deliveryPizza() {
        System.out.println("披萨店配送披萨");
    }

    public void orderPizza() {
        takeOrder();
        prepareSource();
        cookPizza();
        packPizza();
        deliveryPizza();
    }
}
public class Customer {

    public void orderPizza(PizzaStore pizzaStore){
        pizzaStore.orderPizza();
    }

    public static void main(String[] args){
        Customer xiaoming = new Customer();
        PizzaStore pizzaStore = new PizzaStore();
        xiaoming.orderPizza(pizzaStore);
    }
}

执行结果跟前面一样,但是现在代码发生了一些变化,我们在PizzaStore类中新增了一个orderPizza()方法来封装所有的披萨定制流程,同时PizzaStore类中的其他方法都被声明为了private,对于小明来说他只需要调用PizzaStore的orderPizza()方法来预定披萨就可以了,不需要知道其内部的运转流程,并且现在实际上小明也无法调用PizzaStore的其他方法了,因为他看不到了。这就是迪米特法则所要求的尽量减少两个实体之间的交互。

这里我也可以使用另外一种方式来满足迪米特法则,那就是引入中间类,通过中间类来解耦调用者和被调用者,这里我们可以引入一个PizzaStoreProxy来解耦Customer和PizzaStore, 类图设计如下:

可以看到我们现在的Customer只跟PizzaStoreProxy进行依赖,而PizzaStoreProxy内部关联PizzaStore,相关代码:

public class PizzaStoreProxy {
    private PizzaStore pizzaStore;

    public PizzaStoreProxy(PizzaStore pizzaStore) {
        this.pizzaStore = pizzaStore;
    }

    public void orderPizza() {
        pizzaStore.takeOrder();
        pizzaStore.prepareSource();
        pizzaStore.cookPizza();
        pizzaStore.packPizza();
        pizzaStore.deliveryPizza();
    }
}
public class Customer {

    public void orderPizza(PizzaStoreProxy pizzaStoreProxy){
        pizzaStoreProxy.orderPizza();
    }

    public static void main(String[] args){
        Customer xiaoming = new Customer();
        PizzaStore pizzaStore = new PizzaStore();
        PizzaStoreProxy pizzaStoreProxy = new PizzaStoreProxy(pizzaStore);

        xiaoming.orderPizza(pizzaStoreProxy);
    }
}

可以看到通过引入一个代理类,小明现在只跟披萨店代理打交道,完全不关披萨店的事,也不知道披萨店的内部实现,实现了解耦。

这就是前面提到的结迪米特法则的第三个含义:知道的越少越好,知道的越多对你没有好处。

迪米特法则另外含义:只和直接的朋友交流(only talk to your immedate frineds)。这是什么意思呢,就是说一个对象在调用另一个对象的过程中,或者在依赖另一个对象的方法中,不应该出现被依赖对象以外的其他对象与依赖者产生交互影响。

例02

为了激励公司员工工作的积极性,老板准备给公司的员工发红包,当然发红包这种小事怎么可能劳驾老板亲自动手呢,于是老板找来部门负责人,让他来代为处理,类图设计如下:

public class Boss {
    public void sendRedPacket(GroupLeader groupLeader) {
        List<Employee> memberList = groupLeader.getMemberList();
        for (Employee employee : memberList) {
            groupLeader.giveRedPacket("100元大红包", employee);
        }
    }
}
public class GroupLeader {
    private List<Employee> memberList;

    public List<Employee> getMemberList() {
        return memberList;
    }

    public void setMemberList(List<Employee> memberList) {
        this.memberList = memberList;
    }

    public void giveRedPacket(String packet, Employee employee) {
        employee.setRedPacket(packet);
    }
}
public class Employee {
    private String packet;
    public void setRedPacket(String packet){
        this.packet = packet;
    }
}

修改后的代码中Boss类的sendRedPacket只跟groupLeader对象有关,移除了Employee对象的影响,降低了耦合性,而将发红包的逻辑移到了GroupLeader内部,现实当中也是如此Boss可能只需要给你传一句话即可,具体怎么执行Boss压根不关心,那是你要做的事。

好了,红包发完了,过了一段时间Boss想检查一下公司员工的工作积极性到底有没有提高,准备抽查一下出勤率,但是公司部门有几百号人抽查不过来,所以准备只抽查部门的某一个组的出勤情况。我们修改一下GroupLeader类并添加小组负责人对象:

public class GroupLeader {
    private TeamLeader teamALeader;
    private TeamLeader teamBLeader;
    private TeamLeader teamCLeader;
    public TeamLeader getTeamALeader() {
        return teamALeader;
    }
    public void setTeamALeader(TeamLeader teamALeader) {
        this.teamALeader = teamALeader;
    }
    public TeamLeader getTeamBLeader() {
        return teamBLeader;
    }
    public void setTeamBLeader(TeamLeader teamBLeader) {
        this.teamBLeader = teamBLeader;
    }
    public TeamLeader getTeamCLeader() {
        return teamCLeader;
    }
    public void setTeamCLeader(TeamLeader teamCLeader) {
        this.teamCLeader = teamCLeader;
    }
}

我们的GroupLeader 现在并不直接管理员工了,GroupLeader的手下现在是TeamLeader,总共有三个组的组长分别负责teamA、teamB、teamC,并由三个TeamLeader分别管理各自组的员工。

好了,都闪开,Boss来点人了:

public class Boss {
    public void count(GroupLeader groupLeader) {
        //检查B组的人数
        System.out.println(groupLeader.getTeamBLeader().getMemberList().size());
    }
}

仔细想一下,这里有没有违法迪米特法则呢?

当然有违反,关键是在这一句代码groupLeader.getTeamBLeader().getMemberList().size(),这句代码实际上耦合了多个类,而迪米特告诉我们与直接的朋友交流,这里直接的朋友只有GroupLeader,其他都不是,然而这里的方法调用还返回了TeamLeader对象,这是迪米特法则不希望看到的,即你是我的朋友,但你的朋友不一定是我的朋友。我们应该怎么修改呢,很简单,在GroupLeader里面再封装一个方法:

public class GroupLeader {
    .....
    
    public int getTeamBMemberCount() {
        return this.teamBLeader.getMemberCount();
    }
}

同时在TeamLeader 中也添加统计数量的方法:

public class TeamLeader {
    .....

    public int getMemberCount() {
        return memberList.size();
    }
}

现在,Boss中的方法就只依赖一个对象了:

public class Boss {

    public void count(GroupLeader groupLeader) {
        //检查B组的人数
        System.out.println(groupLeader.getTeamBMemberCount());
    }
}

这个例子告诉我们应该尽量避免getA().getB()getC().getD()这种链式调用的的写法(返回同一个对象类型的除外),类与类的关系是建立在类之间的,不是方法间,一个方法尽量不要引入一个类中不存在的对象。

怎么才能很好的遵循迪米特法则表达的“与直接的朋友交流”呢,在一个对象的方法内,只调用属于以下范围的方法:

  • 该对象本身
  • 被当做方法参数传进来的对象
  • 该方法内所创建或实例化的任何对象
  • 该对象本身的任何成员变量对象
  • 一个很好的遵循迪米特法则的示范代码:
public class Car {
    Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }

    public void start(Key key) {
        Doors doors = new Doors();
        boolean anthorized = key.turn(); //方法参数传入对象的方法
        if (anthorized) {
            engine.start();  //成员对象的方法
            updateDisplay();  //对象本身的方法
            doors.lock();  //本方法内实例的对象的方法
        }
    }

    //更新显示
    public void updateDisplay() {
    }
}

迪米特法则体现的OO特征:封装。

迪米特法则的优点是可降低系统的耦合度,使类与类之间保持松耦合关系,松耦合的关系同时也提高了复用性。但也有缺点,有可能造成大量中转类或跳转次数的增加,增加系统的复杂性,跳转次数越多系统就越复杂从而越难以维护。所以在实际项目中要具体问题具体分析,在松耦合和可维护性之间做一个合理的取舍。

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值