主页 SOLID设计原则系列之--依赖倒置原则
文章
取消

SOLID设计原则系列之--依赖倒置原则

你好,我是猿java。

前面讲述了SOLID中的前4种设计原则,也列举了实际工作中的一些真实代码示例,今天我们将分析最后一种原则:依赖倒置。

什么是依赖倒置原则

依赖倒置原则,英文为:Dependency inversion principle, 简称:DIP。

依赖,顾名思义,就是A代码里面使用了B,A就依赖B,这个是我们最能理解的方式。 倒置,莫非是A依赖B,要转成B依赖A?

接下来就看看作者 Robert C. Martin 对接口依赖倒置原则是怎么定义的:

The Dependency Inversion Principle (DIP) states that high-level modules should not depend on low-level modules; both should depend on abstractions. Abstractions should not depend on details. Details should depend upon abstractions.

依赖倒置原则(DIP)指出高层模块不应该依赖低层模块; 两者都应该依赖于抽象。 抽象不应该依赖于细节。 细节应该取决于抽象。

高层模块不应该依赖低层模块;抽象不应该依赖于细节;如何理解呢? 先看几个示例,帮助我们理解

如何实现依赖倒置原则

高层依赖低层的反例:实现一个通知系统,当用户账户余额不足时通知用户充值,通知的方式有邮箱,短信等,最常规的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Notification{

    public void notify(String type){
        if ("email".equals(type)){
            // email notify logic

        }else if("sms".equals(type)){
            // sms notify logic

        }else if("xxx".equals(type)){
            //
        }
    }
}

该代码违背了高层依赖于低层,每次低层的实现变更,高层都需要关注,同时该代码也违背了开闭原则。

如何解决?

在计算机领域有个经典的理论:可以通过引入一个中间层来解决依赖。

因此上面的问题,我们可以增加一个中间层,比如:消息中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Notification{

    public void notify(Msg msg){
        // 将消息发送给mq
        sendMq(msg);
    }
}

// 邮件通知
public class EmailNotification{

    public void receiveMq(Msg msg){
        // email notify logic

    }
}

// 短信通知
public class SmsNotification{

    public void receiveMq(Msg msg){
        // Sms notify logic

    }
}

通过引入中间层,高层只需要把信息发送给消息中间件,再也不用依赖于下面的通知实现细节。

抽象依赖实现的反例 在实际开发中,如果使用dubbo框架实现服务之间的rpc调用,假如订单系统需要从用户系统获取用户的信息。

通常的做法是:订单系统会依赖用户系统的API,然后通过API调用用户系统,代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
public class Order{
    // UserService 和 User 都是用户系统 API 依赖提供的
    UserService userService;
    public Order(UserService userService){
        this.userService = userService;
    }
    public User getUserIdInfo(){
        // 抽象的rpc接口调用
        User user = userService.getUserInfo(userId);
    }
}

这段代码看起来没有毛病,假如用户系统API升级了(User类中的name改成了username),如果订单系统需要升级用户系统API,那么只要订单系统里面使用到User name的地方,都要修改成username,这个入侵太可怕。 本来订单系统只是一个抽象的接口调用用户系统,但是因为直接依赖了结果值里面的字段,导致用户实现细节的变更直接影响订单系统,如何解决?

在计算机领域有个经典的理论:可以通过引入一个中间层来解决依赖。

可以引进一层防腐层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Order{
    UserAggr userAggr;
    public Order(UserAggr userAggr){
        this.userAggr = userAggr;
    }
    public UserInfo getUserIdInfo(){
        // 抽象的rpc接口调用
        UserInfo user = userAggr.getUserInfo(userId);
        return user;
    }
}


public class UserAggr{
    // UserService 和 User 都是用户系统 API 依赖提供的
    UserService userService;
    public Order(UserService userService){
        this.userService = userService;
    }
    public UserInfo getUserIdInfo(){
        // 抽象的rpc接口调用
        User user = userService.getUserInfo(userId);

        UserInfo info = transfer(user);

        return info;
    }
}

通过上面的代码改造我们可以看出,用户所用的变更都可以在防腐层屏蔽。

SPI机制也是经典的依赖倒置思维, 比如:在java中 数据库驱动加载机制。

总结

  • 依赖倒置原则(DIP)指出高层模块不应该依赖低层模块; 两者都应该依赖于抽象。 抽象不应该依赖于细节。 细节应该取决于抽象。

  • 在计算机领域有个经典的理论:可以通过引入一个中间层来解决依赖。

鸣谢

如果你觉得本文章对你有帮助,感谢转发给更多的好友,关注我:猿java,为你呈现更多的硬核文章。

drawing

新站上线几个月,可百度快照只收录了主页,该如何处理?

为什么spring 不推荐 @Autowired 用于字段注解?