Loading... > 控制反转的英文翻译是 Inversion Of Control,缩写为 IOC。 高层模块(稳定)不应依赖于低层模块(变化),二者应依赖于抽象 抽象(稳定)不应依赖于实现细节(变化),实现细节应依赖于抽象(稳定) 核心就是提出抽象类 # 一、谁依赖谁 > 高层模块会依赖于低层模块 所谓高层模块和低层模块的划分,简单来说就是,在调用链上,**调用者属于高层,被调用者属于低层**。 ```cpp class CriticalFeature { public: void run() { // 执行第一步 step1_.execute(); // 执行第二步 step2_.execute(); ... } private: Step1 step1_; Step2 step2_; ... } ``` 这种未经审视的结构天然就有一个问题:高层模块会依赖于低层模块。CriticalFeature 类就是高层类,Step1 和 Step2 就是低层模块。 ## 1、控制反转 > 控制反转的英文翻译是 Inversion Of Control,缩写为 IOC 框架提供了一个可扩展的代码骨架,用来组装对象、管理整个执行流程。程序员利用框架进行开发的时候,只需要往预留的扩展点上,添加跟自己业务相关的代码,就可以利用框架来驱动整个程序流程的执行。 而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程可以通过框架来控制。流程的控制权从程序员“反转”到了框架。 **控制反转并不是一种具体的实现技巧,而是一个比较笼统的设计思想,一般用来指导框架层面的设计**。 ```cpp class Handler { public: void execute() { ... Message message = ...; producer_->send(new KafkaRecord("topic", message)); ... } private: KafkaProducer* producer_; } ``` 新需求: 也许你会问,我就是用了 Kafka 发消息,创建一个 KafkaProducer,这有什么问题吗? Kafka 虽然很好,但它并不是系统最核心的部分,我们在未来是可能把它换掉的。 解决办法: 所谓倒置,就是把这种习惯性的做法倒过来,让高层模块不再依赖于低层模块 > `计算机科学中的所有问题都可以通过引入一个间接层得到解决`。All problems in computer science can be solved by another level of indirection—— David Wheeler 是的,引入一个间接层。这个间接层指的就是 DIP 里所说的抽象。不过,在我们课程里,我一直用的说法是模型。 ```cpp class MessageSender { void send(Message message); } class Handler { public: void execute() { ... Message message = ...; sender_->send(message); ... } private: MessageSender* sender_; } ``` 那就要实现一个 Kafka 的消息发送者: ```cpp class KafkaMessageSender : public MessageSender { public: void send(const Message message) { producer_->send(new KafkaRecord("topic", message)); } private: KafkaProducer* producer_; } ``` 这样一来,高层模块就不像原来一样直接依赖低层模块,而是将依赖关系“倒置”过来,让低层模块去依赖由高层定义好的接口。这样做的好处就在于,将高层模块与低层实现解耦开来。  KafkaProducer不稳定,容易变化,而改为MessageSender 之后,Handler依赖于抽象(稳定)的内容。 # 二、依赖于抽象 > 抽象不应依赖于细节,细节应依赖于抽象 在 DIP 的指导下,具体类还是能少用就少用 > 关于 DIP,还有一个形象的说法,称为好莱坞规则:“Don’t call us, we’ll call you”。放在设计里面,这个翻译应该是“别调用我,我会调你的”。显然,这是一个框架才会有的说法,有了一个稳定的抽象,各种具体的实现都应该是由框架去调用。 尽可能把变的部分和不变的部分分开,让不变的部分稳定下来。我们知道,模型是相对稳定的,实现细节则是容易变动的部分。所以,构建出一个稳定的模型层,对任何一个系统而言,都是至关重要的。 这个可以更简单地理解为一点:依赖于抽象,从这点出发,我们可以推导出一些更具体的指导编码的规则: 任何变量都不应该指向一个具体类; 任何类都不应继承自具体类; 任何方法都不应该改写父类中已经实现的方法。 毕竟代码要运行起来不能只依赖于接口。那具体类应该在哪用呢? TODO: 你可以去找一些工具去生成项目的依赖关系图,然后,你就可以用 DIP 作为一个评判标准,去衡量一下你的项目在依赖关系上表现得到底怎么样了。 # 三、依赖注入 > 依赖注入的英文翻译是 Dependency Injection,缩写为 DI 实现控制反转的方式有很多,除了依赖注入,还有模板模式等 ## 1、什么是依赖注入 我们用一句话来概括就是:不通过 new() 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。 在这个例子中,Notification 类负责消息推送,依赖 MessageSender 类实现推送商品促销、验证码等消息给用 ```cpp // 非依赖注入实现方式 class Notification { public: Notification() { messageSender_ = new MessageSender(); //此处有点像hardcode } void sendMessage(String cellphone, String message) { //...省略校验逻辑等... messageSender_.send(cellphone, message); } private: MessageSender messageSender_; } class MessageSender { public: void send(String cellphone, String message) { //.... } } // 使用Notification Notification notification = new Notification(); ``` 通过**依赖注入的方式**来将依赖的类对象传递进来,这样就提高了代码的扩展性,我们可以灵活地替换依赖的类。这一点在我们之前讲“开闭原则”的时候也提到过。 ```cpp // 依赖注入的实现方式 class Notification { public: Notification(MessageSender messageSender) { messageSender_ = messageSender; } void sendMessage(String cellphone, String message) { //...省略校验逻辑等... messageSender_.send(cellphone, message); } private: MessageSender messageSender_; // 通过构造函数将messageSender传递进来 } //使用Notification MessageSender messageSender = new MessageSender(); Notification notification = new Notification(messageSender); ``` 继续优化 ```cpp class Notification { public: Notification(MessageSender messageSender) { messageSender_ = messageSender; } void sendMessage(String cellphone, String message) { messageSender_.send(cellphone, message); } private: MessageSender messageSender_; } class MessageSender { public: void send(String cellphone, String message) = 0; } // 短信发送类 class SmsSender : public MessageSender { @Override public: void send(String cellphone, String message) override { //.... } } // 站内信发送类 class InboxSender : public MessageSender { @Override public: void send(String cellphone, String message) override { //.... } } //使用Notification MessageSender messageSender = new SmsSender(); Notification notification = new Notification(messageSender); ``` ## 2、依赖注入框架(DI Framework) 在采用依赖注入实现的 Notification 类中,虽然我们不需要用类似 hard code 的方式,在类内部通过 new 来创建 MessageSender 对象,但是,这个创建对象、组装(或注入)对象的工作仅仅是被移动到了更上层代码而已,还是需要我们程序员自己来实现。具体代码如下所示: ```cpp class Demo { public: static void function(String args[]) { MessageSender sender = new SmsSender(); //创建对象 Notification notification = new Notification(sender);//依赖注入 notification.sendMessage("13918942177", "短信验证码:2346"); } } ``` 这个框架就是“**依赖注入框架**”。我们只需要通过依赖注入框架提供的扩展点,简单配置一下所有需要创建的类对象、类与类之间的依赖关系,就可以实现由框架来自动创建对象、管理对象的生命周期、依赖注入等原本需要程序员来做的事情。 最后修改:2025 年 07 月 01 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏