为啥依赖注入能比直接new 对象降低耦合度,我总是不理解

zyq654321 2021-05-23 07:17:25
比如A对象里面要调用B对象,B要调用C对象....

ClassA {
public void mathod1() {

Class B b1=new ClassB();
b1.mathodb1();
}
}

ClassB {
public void mathodb1() {

Class C C1=new ClassC();
C1.mathodc1();
}
}

ClassC {
public void mathodc1() {
.....
}
}
我想用ClassA的 mathod1 ,直接(new ClassA()).mathod1()有啥不好呢,非要注入一个ClassA,不嫌麻烦吗
...全文
20560 25 打赏 收藏 举报
写回复
25 条回复
切换为时间正序
请发表友善的回复…
发表回复

创建对象 和 使用对象 分离。
你这里写的是最简单的new的方式,如果是复杂依赖、多入参。那可维护性就要降低很多了。

  • 打赏
  • 举报
回复
幕后眼光 2021-12-10

细想一下,未来你新增一个 class D,你要改 class A 内部代码,这就违反“开闭原则”了,通过传参(注入)的形式,可以达到解耦,方便后续扩展。

  • 打赏
  • 举报
回复
灵魂孤独者 2021-11-13

最直观的好处有两点:
1.默认使用单例模式能大大减少JVM堆内存使用;
2.【依赖注入 最主要的作用是 控制反转】稍微修改下楼主的例子:
场景:除了B依赖A之外还有,C,D,E,F,G等等都依赖A。
如果都直接使用new A()的方式来使用A。那么当业务发生改变发现A不在再满足需求需要调整业务逻辑有了新的实现类A1,那就得重新修改所有使用A的B,C,D,E,F,G等等使用类的代码才能实现。而如果使用依赖注入的方式,就只需要在配置的一个地方修改即可。

  • 打赏
  • 举报
回复 4
qq_39936465 2021-05-27
引用 楼主 zyq654321 的回复:
比如A对象里面要调用B对象,B要调用C对象.... 我想用ClassA的 mathod1 ,直接(new ClassA()).mathod1()有啥不好呢,非要注入一个ClassA,不嫌麻烦吗
耦合性的概念就是代码对参数的依赖紧密度,依赖注入其实是一种代码和参数分离,你可以把配置文件看作一个参数列表,所有代码都是去参数列表找参数,这样当需要修改参数时我们不需要关心代码,只需要修改参数列表,这样自然降低了耦合度。
  • 打赏
  • 举报
回复
千梦一生 2021-05-27
引用 8 楼 zyq654321 的回复:
【依赖注入 最主要的作用是 控制反转】 ,反转的目的不是为了降低耦合吗,否则反转干啥呢,还要配置一大堆东西,不是更加麻烦吗,
误区:降低耦合和减少代码量是无关的。甚至说来很多时候解耦恰恰增加代码量。会“麻烦”起来 几年前学过依赖注入的概念,不知道我记得是否准确。 其实就是一个接口问题。 工厂给的是一个类似于车辆底盘的东西。 你可以想一个问题【可否每个模块设计时,接口不去考虑和其他模块匹配的问题。当然如果真这样,那必然会出现两个模块无法联通工作。如何是好?这时候需要设计一个中间件。此时,这些接口匹配问题均由中间件解决。每个模块仍然各独立。只需要考虑的是各个模块如何接入中间件这个问题】【这就很像现实中的转接线如果设计一个车辆底盘,标准化了允许接入的轮胎大小,螺丝规格之后。那么轮胎、方向盘按要求整就放的进去。它们根本不关心这个主题是一直鸡,还是一盘麻婆豆腐。修改需求轮胎需要改成正方形,只需要改轮胎,其它任何地方都不需要变动
  • 打赏
  • 举报
回复
青阳令 2021-06-21
@千梦一生 你说的好像是接口的概念啊。
  • 举报
回复
xiaoxiangqing 2021-05-27
注入就相当于一个静态对象,所有的都是用的同一个
  • 打赏
  • 举报
回复
groovy2007 2021-05-27
这个要从依赖注入的历史说起。这里有个例子 https://www.w3cschool.cn/wkspring/t7n41mm7.html
假设我们开发一款文本编辑器,其中用到了一个SpellChecker。另外假设有多个不同的SpellChecker实现。我们不希望在代码里写死用哪个SpellChecker,这样不够灵活。代码如下:

public class TextEditor {
private SpellChecker spellChecker;
public TextEditor(SpellChecker spellChecker) {
System.out.println("Inside TextEditor constructor." );
this.spellChecker = spellChecker;
}
public void spellCheck() {
spellChecker.checkSpelling();
}
}

如何在创建程序的时候指定用哪个SpellChecker呢?Spring早期的做法是通过xml配置文件:

<bean id="textEditor" class="com.tutorialspoint.TextEditor">
<constructor-arg ref="spellChecker"/>
</bean>

<bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
</bean>

这样的话,spring就帮你创建了一个textEditor,并且帮你“注入”了一个spellChecker。如果想更换spellChecker只需要改变配置文件就行了,不需要重新编译代码。

可是现在大家都不这么写了,基本上都是用注解。

public class TextEditor {
private SpellChecker spellChecker;

// 自动寻找SpellChecker的默认实现,如果只有一个实现的话。或者通过其他注解显式指定用哪个实现。
@autowired
public TextEditor(SpellChecker spellChecker) {
System.out.println("Inside TextEditor constructor." );
this.spellChecker = spellChecker;
}
public void spellCheck() {
spellChecker.checkSpelling();
}
}

这样其实和硬编码差不多(可能少敲几个字符吧,new MySpellChecker()),有任何改动还是需要重新编译。TextEditor又和具体的SpellChecker耦合在了一起。

为什么会发生这种转变?可能大家发现绝大多数的接口其实只有一个实现,而且也不太可能在别的项目里重用,所以耦合就耦合了吧。
  • 打赏
  • 举报
回复 6
青阳令 2021-06-21
@groovy2007 解释的很好,受教啦
  • 举报
回复 1
@groovy2007 谢谢学到了
  • 举报
回复
qybao 2021-05-26
你是真不明白我所说的内容,还是故意钻牛角尖? 这里所谓的不改变代码是指整个处理流程(或框架)的代码不改变。你增加新的业务,当然要加新的业务实现类代码(代码不会凭空生成),但这新增的业务实现代码不影响你的主处理,也就是相当于例子中的class Main(不变指的是这里)。如果不用注入,你的class Main每次都要修改。当然,你也可以用些设计模式来达到Main不变,但毕竟这些设计模式的代码是由你自己来维护,你能保证这些代码有多大的扩展性(能确保今后都不用修改)?即然有成熟的框架,为何不直接使用?尤其是在追求开发效率的当下。
  • 打赏
  • 举报
回复 1
zyq654321 2021-05-26
谢谢大家的热情讨论,通过讨论和网上查找资料,我基本理解了注入依赖,依赖注入主要目的是为了降低耦合,单例模式能大大减少JVM新生代eden区内存 ,对使用频率超高的类有用 ,但是会长期占用JVM静态区和堆区的老年代,使用频率不高的东西使用单例模式弊大于利,所以一定要理解各种模式才能真正地用好。
  • 打赏
  • 举报
回复
冰思雨 2021-05-26
我和楼主有不同的理解。 依赖注入的主要目的,应该和降低耦合没有什么关系,依赖注入的主要目的应该是代码的复用。 通过配置各个类之间的依赖关系,可以灵活的复用这些类的代码(功能)或者资源(不仅仅是内存数据资源,还包括文件,网络等等,看项目需要)。 楼主说的降低耦合性,这个不是没有只是,不是主要目的。 A 和 B 有依赖关系,如果不适用DI,自己创建的话,是明显的紧密耦合;如果使用DI(依赖注入)的话,A 和 IoC 容器耦合了,B 也和 IoC 容器耦合了,A 和 B 的耦合关系,被 IoC 容器转嫁了。其实就是,原本两个类的夫妻关系,现在变成特殊的三角关系了。我并不觉得这对软件设计有什么出彩的好处。 当然,在使用 IoC 容器的时候,面向接口编程,可以更好的进行降低耦合或者解除耦合,但是,这个与楼主说的应该不是一个主题。 另外, [怎么在不改变代码的前提下,生成不同的实现类a] 这个是要体现多态还是有什么具体的业务场景要求这样呢? 如果是要处理业务逻辑的话,可以参考策略模式,这个很好实现的。 [怎么在不改变代码的前提下,扩展a的属性] 这个可以参考装饰器模式来实现。 当然,以我的理解,这些好像都和楼主说的内容,不是同一个主题吧。 依赖注入就是一种设计思想,把类(对象)直接的关系通过配置文件的形式体现出来,然后,由容器进行注入。仅此而已。 你要非说它能降耦吧,也是可以的,反正这么一折腾,所有依赖注入的对象,都和容器有耦合关系。彼此的耦合关系被转嫁到容器上面了一些。
  • 打赏
  • 举报
回复
maradona1984 2021-05-25
"依赖注入能比直接new 对象降低耦合度"这个前提是依赖接口,如果你是直接依赖实现类(当然也可以注入子类),注入的意义大概也就统一的规范和aop了
  • 打赏
  • 举报
回复
=PNZ=BeijingL 2021-05-24
我想用ClassA的 mathod1 ,直接(new ClassA()).mathod1()有啥不好呢,非要注入一个ClassA,不嫌麻烦吗

注入的方式能实现你处理的ABC三个对象都是唯一的,对象里持有的属性访问都是对象的实时的数据
如果用new的方式,比如(new ClassA())是新创建1个对象,这样A对象里的属性都是新初始化的,
你考虑下这个场景: 如果在同一个程序中, 每次都new 对象, 那么对象中持有的属性的数据都会丢失,数据很难传递,
  • 打赏
  • 举报
回复 1
zyq654321 2021-05-24
[所以,你思考一下,怎么在不改变代码的前提下,生成不同的实现类a,怎么在不改变代码的前提下,扩展a的属性。想明白这两个问题,你就明白注入的意义了。] 你搞错了把,不写代码哪里来的不同的实现类
  • 打赏
  • 举报
回复
a1767028198 2021-05-24
[所以,你思考一下,怎么在不改变代码的前提下,生成不同的实现类a,怎么在不改变代码的前提下,扩展a的属性。想明白这两个问题,你就明白注入的意义了。]很精辟,多读读几次多理解一下,
  • 打赏
  • 举报
回复
qybao 2021-05-24
这个问题,有那么难理解吗? 来看这么一个例子 interface IA {} //业务处理接口 class A1 implements IA {} //业务实现类 class Main { // 注入IA实例 IA a; private dosomething() { a.xxx(); //直接使用a } } 好了,再来讨论换个业务实现类 class A2 implements IA {} 此时,用注入的方式,只要修改配置文件即可,代码不用改 如果不用注入的方式,那么在dosomething里a就要自己去new,这种动态的new,你不知道业务到底是什么实现类,没法控制。就算你用反射,也是有局限,因为没法动态a的属性(假设业务实现类a的属性有所变化),而配置文件,只需要再配置a的property即可,代码还是不用变。 所以,你思考一下,怎么在不改变代码的前提下,生成不同的实现类a,怎么在不改变代码的前提下,扩展a的属性。想明白这两个问题,你就明白注入的意义了。
  • 打赏
  • 举报
回复 2
zyq654321 2021-05-24
【如果采用楼主的那种编程方式,每调用一次函数,都现场 new 一个对象,然后调用对象的方法,这样的话,如果频繁调用函数,意味着函数内部会频繁的创建对象,内存使用会出现突发性暴增的情况,当然,也会触发GC。如果使用依赖注入的话,函数内的对象,可能会声明为类的成员变量,而且由于是单例的,每次函数调用,都没有必要再次创建。】这个我说了,是单例模式的优点,不是依赖注入的优点,依赖注入也可以采用多例模式
  • 打赏
  • 举报
回复
zyq654321 2021-05-24
【依赖注入 最主要的作用是 控制反转】 ,反转的目的不是为了降低耦合吗,否则反转干啥呢,还要配置一大堆东西,不是更加麻烦吗,
  • 打赏
  • 举报
回复
zyq654321 2021-05-24
[所以,你思考一下,怎么在不改变代码的前提下,生成不同的实现类a,怎么在不改变代码的前提下,扩展a的属性。想明白这两个问题,你就明白注入的意义了。] 不改变代码的前提 ,如何生成不同的实现类a ,你不是要做另外一个类去实现interface么,这个不用码代码啊,码一个新的类不麻烦,new A改成new B就麻烦了么,还是不理解。
  • 打赏
  • 举报
回复
冰思雨 2021-05-24
依赖注入 最主要的作用是 控制反转 , 可以理解为一个大型的对象工厂,里面的对象统一由这个对象工厂来管理。 至于楼主说的降低耦合,应该是通过面向接口编程来实现的,至少我是这么理解的。 楼主举的这个例子,即没有体现控制反转,也没有体现面向接口的编程思想,所以,无法看出可以降低耦合这个特性。 想要降低耦合,主要还是看软件设计,你的代码设计的好,不使用依赖注入,依然可以低耦合。 所以,降低耦合,其实和 依赖注入,并没有什么必然的关系。 依赖注入,对楼主的样例代码而言,一般情况下,依赖注入的对象,都是默认单例的,这对内存的管理有好处。 如果采用楼主的那种编程方式,每调用一次函数,都现场 new 一个对象,然后调用对象的方法,这样的话,如果频繁调用函数,意味着函数内部会频繁的创建对象,内存使用会出现突发性暴增的情况,当然,也会触发GC。如果使用依赖注入的话,函数内的对象,可能会声明为类的成员变量,而且由于是单例的,每次函数调用,都没有必要再次创建。 另外,降低耦合,必然要向 加强封装特性 和 接口隔离 这两个方向努力。封装方面,设计类的时候成员变量和函数要抽象的得体一些;接口隔离方面,隔离要尽量彻底,尽量避免依赖关系才行。
  • 打赏
  • 举报
回复
zyq654321 2021-05-24
注入的方式能实现你处理的ABC三个对象都是唯一的,使用单例模式就可以,不需要注入, 那么对象中持有的属性的数据都会丢失,数据很难传递---》你搞反了, 高内聚,低耦合的思想是尽量不要保持对象的数据,
  • 打赏
  • 举报
回复
加载更多回复
相关推荐
发帖
Java EE
加入

6.7w+

社区成员

J2EE只是Java企业应用。我们需要一个跨J2SE/WEB/EJB的微容器,保护我们的业务核心组件(中间件),以延续它的生命力,而不是依赖J2SE/J2EE版本。
社区管理员
  • Java EE
申请成为版主
帖子事件
创建了帖子
2021-05-23 07:17
社区公告
暂无公告