设计心得——依赖注入控制反转和依赖倒置

一、介绍

在设计的过程中,最主要的目的就是实现“高内聚低耦合”。其实不耦合是最好的,但正如人是群体性动物一样,不和别的人发生各种关系是不可能的。而把这种发生的各种关系移植到代码上,就可以称之为依赖。这种依赖越少,就说明耦合程度越低(即解耦);反之则耦合程度越高。
高内聚则是指,单元(一个函数,一个类等都可以是一个单元)内功能的依赖关系越紧密越好,也可以理解为相关功能关联度越紧密越好。
那么要实现“高内聚低耦合”则可以理解为对依赖关系的处理,而在对依赖关系的处理中,在设计中经常遇到的有三个概念即依赖倒置、控制反转和依赖注入。估计即使没有做过的设计开发者也都听过其中的一个两个。
依赖倒置是一个总的原则,即常说的“依赖于抽象而不依赖于具体实现”,也就是上层模块不直接依赖于底层模块而是要通过抽象来实现相互依赖。为了实现这个原则提出了控制反转,即对象的创建控制由生产方转移到第三方(当然也可以反过来理解,控制方并不生产对象)。而更进一步为了实现控制反转,提出了依赖注入。可以这样理解,这三个是一层层的从抽象向实现的演进过程。下面就对这三个概念进行分析说明。

二、依赖倒置

DIP,Dependency Inversion Principle,依赖倒置,也叫反向依赖或依赖反转。

#include <iostream>
#include <memory>

// 抽象基类
class MsgSender {
public:
    virtual ~MsgSender() = default;
    virtual void sendMsg(const std::string& msg) = 0;
};

// 具体实现类
class TelSender : public MsgSender {
public:
    void sendMsg(const std::string& msg) override {
        std::cout << "Tel send: " << msg << std::endl;
    }
};

class QqSender : public MsgSender {
public:
    void sendMsg(const std::string& msg) override {
        std::cout << "Sending QQ: " << msg << std::endl;
    }
};

// 依赖抽象基类
class NoticeService {
private:
    std::unique_ptr<MsgSender> MsgSender;

public:
    NoticeService(std::unique_ptr<MsgSender> sender)
        : MsgSender(std::move(sender)) {}

    void notify(const std::string& msg) {
        MsgSender->sendMsg(msg);
    }
};

int main() {
    // 创建具体的实现类对象
    std::unique_ptr<MsgSender> TelSender = std::make_unique<TelSender>();
    std::unique_ptr<MsgSender> QqSender = std::make_unique<QqSender>();

    //注入具体的实现类
    NoticeService notifyTel(std::move(TelSender));
    NoticeService notifyQq(std::move(QqSender));

    // 应用
    notifyTel.notify("this is Tel sender.");
    notifyQq.notify("this is QQ sender.");

    return 0;
}

在上面的代码中,注意是依赖关系由抽象类MsgSender进行管理,也就是上面提到的依赖于抽象而不依赖于具体实现。很好的实现了对消息传递实现者的解耦,从而让程序有较好的可扩展性。

三、控制反转

IoC,inversion of control,控制反转。实现控制反转的手段有很多,包换下面的依赖注入以及依赖查找等等。其实在Java中控制反转非常好理解,常见的Spring系列就是如此。对于C++来说,由于类似的框架没有在Java中这种统一的应用,所以谈起来可能对很多C++开发者有一些陌生。这里只举一个例子大家就明白了,比如大家设计一个汽车喷漆的机器人,机器人要可以给各种类型汽车的各种型号的漆进行喷涂。那么就需要由外界提供一个漆的容器可以动态的将各种类型的漆输入到机器人中,机器人只管按照命令执行喷涂的动作,而不需要对漆的颜色、类型等进行控制。这个漆的容器就可以理解为一个IoC容器。而如何设计这个容器,既保证能动态的切换各种漆,又要保障漆之间的不互相污染,其实就是控制反转具体实现的手段。
其实从更抽象的层面看,货运中的集装箱就可以看成一个IOC容器,不过它里面的对象,需要自己查找,而无法进行自动分发(实际的自动分发,不是通过容器进行的,是另外一回事)。
控制反转的例子和依赖注入共用一个(看下面的代码)。

四、依赖注入

DI,Dependency Injection,依赖注入。所谓依赖注入其实就是获取由它方创建的对象来实现依赖关系即依赖关系是由它方对象注入自身中实现。它有三种实现方式:构造器注入、setter方法注入、接口注入。

#include <iostream>
#include <memory>
#include <string>
#include <functional>

// 日志抽象类接口
class Logger {
public:
    virtual ~Logger() = default;
    virtual void log(const std::string& msg) = 0;
};

// 具体的日志类
class RealTimeLogger : public Logger {
public:
    void log(const std::string& msg) override {
        std::cout << "LOG: " << msg << std::endl;
    }
};

// 日志服务类
class Service {
private:
    std::shared_ptr<Logger> logger; // 使用智能指针管理Logger的生命周期

public:
    // 构造函数注入Logger依赖
    Service(std::shared_ptr<Logger> logger) : logger(std::move(logger)) {}

    void logAction() {
        logger->log("write log!");
        //其它工作
    }
};

int main() {
    auto logger = std::make_shared<RealTimeLogger>();

    // 注入Logger依赖
    Service srv(logger);
    srv.logAction();

    return 0;
}


大家需要搞清楚的是,依赖注入只是实现控制反转的一种方式,但也是非常常见的一种方式。所以在很多资料和书籍中,就经常将二者混同。但是一定要明白,这样说是不准确的或者说不严格的。不过在实际应用中,这种实现方式确实又是主流的情况,明白这一点就好。
最后简介绍一下Dependency Lookup,依赖查找。它是一种适合于静态的、中小型,特别是小型工程的一种方式,它只提供依赖的对象管理,通过API来手动的管理依赖关系,更容易被理解和应用。但相对来说增加了一些耦合度并且手动管理少量的对象还是可控的,当增加到一定程度后,出现问题的机率便会大大增加。

五、应用场景

在这些原则和实现方式的应用场景中,最多的就是进行容器框架的场景了。比如Boost库的依赖注入框架Boost.DI。但对于很多开发者来说,一般不会轻易涉及到这些框架或库的编写,更多的常见的如日志的动态控制、测试的控制、配置的控制、插件管理等以及其它类似于有这种模块解耦需求的方式都可以使用。

六、总结

上述的应用只是一个基础的代码描述,其实引入一些设计模式特别是模板,可能会产生更好的效果。这就需要大家不断的在实践中尝试和总结相关的技巧。当然,如果自己能够根据这些思想和原则写一个类似于Spring中的IoC框架更是能深刻的掌握这些知识。
掌握设计相关的概念是基础,在这个基础上,弄明白其内在的涵义才是至关重要的。将这些基础学会学扎实并融会贯通,然后能够将其应用到适合的应用场景中去。恭喜,你就掌握了最基础的设计能力。这也是走向自己的设计思想之路。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值