依赖倒置和控制反转

article/2025/11/6 20:04:27

依赖倒置

定义

依赖反转原则(Dependency inversion principle,DIP)是指一种特定的解耦形式,使得高层次的类不依赖于低层次的类的实现细节,依赖关系被颠倒(反转),从而使得低层次类依赖于高层次类的需求抽象。

该原则规定:

  1. 高层次的类不应该依赖于低层次的类,两者都应该依赖于抽象接口。
  2. 抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口。

在传统的应用架构中,低层次的组件设计用于被高层次的组件使用,这一点提供了逐步的构建一个复杂系统的可能。在这种结构下,高层次的组件直接依赖于低层次的组件去实现一些任务。这种对于低层次组件的依赖限制了高层次组件被重用的可行性。

依赖反转原则的目的是把高层次组件从对低层次组件的依赖中解耦出来,这样使得重用不同层级的组件实现变得可能。

听起来很干,我们先通过实现一个简单的需求来描述依赖倒置原则要解决的问题。

需求

假设我们要实现一个简单的缓存功能,主要负责把数据存储起来方便以后调用。

为了快速完成需求,我们决定采用最简单的实现方式:存储到文件,所以我们设计其结构如下:

图 1 显示在应用程序中一共有两个类。“Cache” 类负责调用"FileWrite"类来写入文件。代码实现如下:

<?phpclass FileWriter{public function writeToFile($key,$value=null){//....}
}class Cache{protected FileWriter $file_writer;public function __contruct(){$this->file_writer=new FileWriter();}public function set($key,$value=null){$this->file_writer->writeToFile($key,$value);}
}

在所有使用文件来作为存储方式的系统中,上面的"Cache"类能很好的使用,然而在以其他方式来存储的系统中,“Cache” 类是无法被重用的。

例如,假设我们的系统是分布式服务,部署在多台机器上,这时候如果使用文件存储则缓存只能生效于本机器上,无法被其他机器使用,故我们无法使用文件来作为缓存的存储方式,所以我们引入一个新的存储方式:redis。另外我们也希望复用 “Cache” 类,但很不幸的是, “Cache” 类是直接依赖于 “FileWrite” 类的,无法直接被重用。

为了解决这个问题,我们需要修改下代码,如下:

<?phpclass FileWriter{public function writeToFile($key,$value=null){//....}
}class RedisWriter{public function writeToRedis($key,$value=null){//....}
}
class Cache{protected FileWriter $file_writer;protected RedisWriter $redis_writer; protected $type;public function __contruct($type){$this->type=$type;if($this->type=='redis'){$this->file_writer=new FileWriter();}else if($this->type=='file'){$this->redis_writer=new RedisWriter();}}public function set($key,$value=null){if($this->type=='redis'){$this->file_writer->writeToRedis($key,$value);}else if($this->type=='file'){$this->file_writer->writeToFile($key,$value);}}
}

可以看到我们需要引入一个新的类,而且需要去修改"Cache"类的代码。随着需求的变化,我们可能有要支持其他的存储方式,例如数据库,memcached,这时候我们就需要不断添加新的类,不断修改"Cache"类,而且把"Cache"淹没在凌乱的"if/else"判断中,这样的设计的维护和拓展成本简直不可想象。

出现这些问题的原因就在于类间的相互依赖,主要特征是包含高层逻辑的类依赖于低层类的细节:"Cache"类的"set"功能完全依赖于下面的"FileWriter"的具体实现细节,导致在使用环境发生变化的时候,"Cache"类无法复用。依赖倒置原则就是为了来解决这个问题的。

问题

  • 没有抽象,耦合度高:当低层模块变动时,高层模块也得变动;
  • 高层模块过度依赖低层模块,很难扩展。
  • 这种依赖关系具有传递性,即如果是多层次的调用,最低层改动会影响较高层……直到最高层。

解决

高层次的类不应该依赖于低层次的类,两者都应该依赖于抽象接口:例如 “Cache” 类依赖于 “FileWriter” 类的实现,所以才会无法适用使用环境的变化。所以我们要想办法使 “Cache” 类不依赖于这些细节,因为具体实现是不断变化的,而抽象接口是相对稳定的,所以我们要把数据的存储抽象出来,成为一个接口,针对这个接口进行编程,这样就无需面对频繁变化的实现细节。

抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口:在一开始做设计的时候,我们不要去考虑具体实现,而应该根据业务需求去设计接口,例如上面的例子,我们一开始就已经考虑到了用文件来存储了,而且架构也是基于此来进行设计的,这就从一开始是高层次的类依赖于具体实现了。然而实际上我们需要的功能是:数据存储,把数据存储到某个地方,至于具体怎么存储我们其实并不需要关心,只需要知道能存即可,所以我们一开始设计的时候就不应该针对文件存储来进行编程,而应该针对抽象的存储接口来编程。同时具体实现也依据接口来进行编程。

因此我们优化后的架构如下:

此时类 “Cache” 既没有依赖 “FileWriter” 也没有依赖 “RedisWriter”,而是依赖于接口"Writer",同时"FileWriter" 和 “RedisWriter” 的具体实现也依赖于抽象。

<?phpinterface Writer{public writer($key,$value=null);
}
class FileWriter implement Writer{public function write($key,$value=null){//....}
}class RedisWriter{public function write($key,$value=null){//....}
}
class Cache{protected Writer $writer;public function __contruct(){//$this->wirter=new RedisWriter();$this->wirter=new FileWriter();}public function set($key,$value=null){$this->file_writer->write($key,$value);}
}

此时,我们就可以重用 “Cache” 类,而不需要具体的"Writer"。在不同的环境条件下,我们只需要修改生成的"writer"类即可,"set"方法里面的逻辑完全不需要改动,因为这里面是针对抽象接口"Writer"编程,只要"Writer"没有变,"set"方法也不需要做任何修改。

使用场景

程序中所有的依赖关系都应该终止于抽象类或者接口中,而不应该依赖于具体类。
根据这个启发式规则,编程时可以这样做:

  • 类中的所有成员变量必须是接口或抽象,不应该持有一个指向具体类的引用或指针。
  • 任何类都不应该从具体类派生,而应该继承抽象类,或者实现接口。
  • 任何方法都不应该覆写它的任何基类中已经实现的方法。(里氏替换原则)
  • 任何变量实例化都需要实现创建模式(如:工厂方法/模式),或使用依赖注入框架(如:Spring IOC)。

优点

  • 高层模块和低层模块彻底解耦,都很容易实现扩展
  • 抽象模块具有很高的稳定性、可重用性,对高/低层模块来说才是真正"可依赖的"。

缺点

  • 增加了一层抽象层,增加实现难度;
  • 对一些简单的调用关系来说,可能是得不偿失的。
  • 对一些稳定的调用关系,反而增加复杂度,是不正确的。

控制反转

说完依赖倒置,我们再来说一个很相似的设计原则:控制反转。

看看上面的代码,虽然"set"方法里的逻辑不会发现变化了,但是在构造函数里还是要根据不同环境来生成对应的"Writer",还是需要修改代码,为了解决这个问题,我们引入控制反转的原则。

定义

控制反转(Inversion of Control,缩写为IoC ),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

控制反转针对的是依赖对象的获得方式,也既依赖对象不在是自己内部生成,而是由外界生成后传递进来。如下代码:

<?phpinterface Writer{public writer($key,$value=null);
}
class FileWriter implement Writer{public function write($key,$value=null){//....}
}class RedisWriter{public function write($key,$value=null){//....}
}
class Cache{protected Writer $writer;public function __contruct(Writer $writer){$this->wirter=$writer}public function set($key,$value=null){$this->file_writer->write($key,$value);}
}

"Cache"把内部依赖"Writer"的创建权力移交给了上层模块,自己只关心依赖提供的功能,但并不关心依赖的创建。IoC 是一种新的设计模式,它对上层模块与底层模块进行了更进一步的解耦。

实现方式

实现控制反转主要有两种方式:依赖注入和依赖查找。两者的区别在于,前者是被动的接收对象,在类A的实例创建过程中即创建了依赖的B对象,通过类型或名称来判断将不同的对象注入到不同的属性中,而后者是主动索取相应类型的对象,获得依赖对象的时间也可以在代码中自由控制。

依赖注入

依赖注入有如下实现方式:

  • 基于构造函数。实现特定参数的构造函数,在新建对象时传入所依赖类型的对象。
  class Cache{protected Writer $writer;public function __contruct(Writer $writer){$this->wirter=$writer}}
  • 基于 set 方法。实现特定属性的public set方法,来让外部容器调用传入所依赖类型的对象。
  class Cache{protected Writer $writer;public function setWriter(Writer $writer){$this->wirter=$writer}}
  • 基于接口。实现特定接口以供外部容器注入所依赖类型的对象。
  interface WriterSetter {public function setWriter(Writer $writer);}class Cache implement WriterSetter{protected Writer $writer;@Overridepublic function setWriter(Writer $writer){$this->wirter=$writer}}

接口注入和setter方法注入类似,不同的是接口注入使用了统一的方法来完成注入,而setter方法注入的方法名称相对比较随意,接口的存在,表明了一种依赖配置的能力。

在软件框架中,读取配置文件,然后根据配置信息,框架动态将一些依赖配置给特定接口的类,我们也可以说 Injector 也依赖于接口,而不是特定的实现类,这样进一步提高了准确性与灵活性。

依赖查找

依赖查找相比于依赖注入更加主动,先配置好对象的生成规则,然后在需要的地方通过主动调用框架提供的方法,根据相关的配置文件路径、key等信息来获取对象。

例如lumen里面:

app()->bind('classA', function ($app) {return new ClassA();
});//使用
$classA=app()->make('classA');

参考

https://zh.wikipedia.org/wiki/%E4%BE%9D%E8%B5%96%E5%8F%8D%E8%BD%AC%E5%8E%9F%E5%88%99
https://zh.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC
https://blog.csdn.net/briblue/article/details/75093382


Enjoy it !

如果觉得文章对你有用,可以赞助我喝杯咖啡~

版权声明

转载请注明作者和文章出处
作者: X先生
https://blog.csdn.net/u013314679/article/details/105655583


http://chatgpt.dhexx.cn/article/aNaID10J.shtml

相关文章

Inversion of Control (IOC)控制反转 有什么好处

要了解控制反转( Inversion of Control ), 我觉得有必要先了解软件设计的一个重要思想&#xff1a;依赖倒置原则&#xff08;Dependency Inversion Principle &#xff09;。 什么是依赖倒置原则&#xff1f;假设我们设计一辆汽车&#xff1a;先设计轮子&#xff0c;然后根据…

Spring学习:IOC控制反转

一、Spring概述&#xff1a; Spring是一个开源框架&#xff0c;其存在的根本使命就是简化JAVA开发。为了降低JAVA开发的复杂性&#xff0c;Spring采取了以下四种关键策略&#xff1a; 基于POJO的最轻量级和最小侵入性编程&#xff1b;通过依赖注入和面向接口实现松耦合&#x…

控制反转(IOC)简介

IOC是Inversion of Control的缩写&#xff0c;多数书籍翻译成“控制反转”&#xff0c;还有些书籍翻译成为“控制反向”或者“控制倒置”。 1996年&#xff0c;Michael Mattson在一篇有关探讨面向对象框架的文章中&#xff0c;首先提出了IOC 这个概念。对于面向对象设计及编程…

IoC 控制反转理解

控制反转——Inversion of Control&#xff0c;缩写为IoC &#xff0c;是一个重要的面向对象编程的法则&#xff0c;说到底它是一种设计思想&#xff0c;其可以降低程序中的耦合度&#xff0c;在以前&#xff0c;我们通过new进行创建对象&#xff0c;这是程序主动去创建依赖对象…

IOC控制反转理解

1. IOC基础 IOC&#xff1a;全称Inversion Of Control&#xff0c;中文翻译是控制反转的意思。初学Spring绕不过去的一个弯&#xff0c;需要好好理解IOC的思想。网上看了很多博客&#xff0c;也看了很多网课&#xff0c;但是还是云里云雾。终于看到一个容易理解的例子&#xf…

C# IoC控制反转学习笔记

一、什么是IOC IoC-Invertion of Control&#xff0c;即控制反转&#xff0c;是一种程序设计思想。 先初步了解几个概念&#xff1a; 依赖&#xff08;Dependency&#xff09;&#xff1a;就是有联系&#xff0c;表示一个类依赖于另一个类。 依赖倒置原则&#xff08;DIP&a…

spring -- 控制反转IOC

1.IOC的理论背景 在面向对象设计的软件系统中&#xff0c;它的底层都是由N个对象构成的&#xff0c;各个对象之间通过相互合作&#xff0c;最终实现系统地业务逻辑。 图1 软件系统中耦合的对象 齿轮组中齿轮之间的啮合关系,与软件系统中对象之间的耦合关系非常相似。对象之间…

Java:控制反转

Java中自带的函数或对象都是编写软件的时候写的&#xff0c;当它需要调用你自己编写的数据的时候&#xff0c;它如何又不知道你定义的类&#xff08;对象&#xff09;是什么&#xff0c;里面有什么成员&#xff0c;它无法调用你的代码&#xff0c;这个时候就需要用到控制反转了…

IoC控制反转

IoC控制反转 一、IoC概念和原理1. 什么是IoC2. IoC底层原理 二、IoC过程1. xml配置文件&#xff0c;配置创建的对象2. 有UserService和UserDao类&#xff0c;创建工厂类 三、IoC接口1. IoC思想基于IoC容器完成&#xff0c;IoC容器底层就是Bean工厂2. Spring提供实现IoC容器的两…

IOC 控制反转

文章目录 一.IOC概念二. IOC的体现三.ioc的技术实现三.基于XML的DI1.引用类型属性自动注入 四.包含关系的配置文件五.基于注解的DI五.简单类型的属性赋值1.vaule(简单类型的属性赋值&#xff09;2.自动注入 一.IOC概念 IOC&#xff1a;控制反转&#xff0c;是一个理论&#xf…

浅谈控制反转(IoC)

Inversion of Control 什么是控制反转&#xff1f; 程序的流程控制权相对于传统的面向过程编程而言发生了反转。下面是维基百科的描述 In software engineering, inversion of control (IoC) is a programming principle. IoC inverts the flow of control as compared to tr…

Spring---浅谈IOC

概念 IOC&#xff08;Inversion of Control 控制反转&#xff09;是spring的核心&#xff0c;贯穿始终。所谓IOC&#xff0c;对于spring框架来说&#xff0c;就是由spring来负责控制对象的生命周期和对象间的关系。 传统开发模式与IOC开发模式的对比 传统开发模式&#xff1a;对…

控制反转-Inversion Of Control

一、控制反转 控制反转&#xff08;Inversion of Control&#xff0c;英文缩写为IoC&#xff09;把创建对象的权利交给框架&#xff0c;是框架的重要特征&#xff0c;并非面向对象编程的专业术语。它包括依赖注入&#xff08;Dependency Injection&#xff0c;简称DI&#xff…

微信小程序hamburgers汉堡包css动画

微信小程序hamburgers汉堡包css动画 源码在我的csdn里下载 https://download.csdn.net/download/ozhy111/12201373

汉堡式折叠html,美味的CSS动画汉堡包:hamburgers_html/css_WEB-ITnose

s if you insist, but they’re not accessible as a menu button. Append the class name of the type of hamburger you’re craving: Here’s the list of hamburger-type classes you can choose from: hamburger--arrowhamburger--arrow-rhamburger--arrowalthamburger--a…

LSB 题解

今天来刷一道Misc的题目&#xff0c;LSB原理进行图片隐写 LSB原理 LSB是一种利用人类视觉的局限性设计的幻术 PNG和BMP图片中的图像像素一般是由RGB(RED红 GREEN绿 BLUE蓝)三原色组成 记住&#xff0c;JPG图片是不适合使用LSB隐写的&#xff0c;JPG图片对像数进行了有损压缩…

LSB利器-zsteg

一、Usage # zsteg -h Usage: zsteg [options] filename.png [param_string] -c, --channels X channels (R/G/B/A) or any combination, comma separated valid values: r,g,b,a,rg,bgr,rgba,r3g2b3,... -l, --limi…

【linux】lsb_release -a命令

查看linux发行版本&#xff1a;lsb_release -a 运行该命令时若提示lsb_release命令不存在 不存在&#xff0c;则表明系统未安装该命令&#xff0c;报错如下&#xff1a; 解决方法&#xff1a; 查看 lsb_release 命令属于哪个软件包&#xff0c;命令为yum provides */lsb_rel…

图片LSB隐写(java)

一、隐写原理 LSB隐写原理就是图片中的像素一般是由三种颜色组成&#xff0c;即三原色(红绿蓝)。由这三种原色可以组成其他各种颜色&#xff0c;在png图片的存储中,每个颜色占有8bit,即有256种颜色&#xff0c;一共包含256的三次方颜色&#xff0c;即16777216中颜色。人类的眼睛…

LSB隐写术

前言 LSB全称为 least significant bit&#xff0c;是最低有效位的意思。Lsb图片隐写是基于lsb算法的一种图片隐写术&#xff0c;以下统称为lsb隐写&#xff0c;这是一种常见的信息隐藏方法。当然关于图像的隐写的方法有很多&#xff0c;统称为隐写术&#xff0c; lsb隐写很实…