java依赖的斗争:依赖倒置、控制反转和依赖注入
控制反转(Inversion Of Controller)的一个著名的同义原则是由Robert C.Martin提出的依赖倒置原则(Dependency Inversion Principle),它的另一个昵称是好莱坞原则(Hollywood Principle):不要找我们,让我们来找你。
依赖和耦合(Dependency and Coupling)
依赖:依赖描述了两个模型元素之间的关系,如果被依赖的模型元素发生变化就会影响到另一个模型元素。
简单的说,依赖就是一种需要。鱼需要水才能生存,鱼对水就有依赖关系;人需要进食才能活着,人对食物就有依赖关系。
耦合:如果改变程序的一个模块要求另一个模块同时发生变化,就认为这两个模块发生了耦合。
简单地说,耦合就是发生了依赖。和上面的例子一样,鱼和水之间发生了耦合,如果水发生了改变,会影响到鱼;人和食物之间发生了耦合,如果食物发生了改变,也会对人造成影响。
从上面的定义中可以看出,如果模块A调用模块B提供的方法,或者访问模块B中的某些数据成员(当然,在面向对象开发中一般不提倡这样做),我们就认为模块A依赖于模块B,模块A和模块B之间发生了耦合。
class A {
B b = new B(); // 对B产生了依赖关系,即A和B发生了耦合关系
void doA() {
b.doB();
}
} class B {
void doB() {
// do something
}
}
那么,依赖对于我们来说到底是好事还是坏事呢?
由于人类的理解力有限,大多数人难以理解和把握过于复杂的系统(大神或天才除外),因此,把软件系统划分为多个模块,可以有效控制模块的复杂度,使每个模块都易于理解和维护。但在这种情况下,模块之间就必须以某种方式交换信息,也就是说,必然要发生某种耦合关系。如果某个模块和其它模块没有任何关联(哪怕是潜在的或隐含的依赖关系),我们就几乎可以断定,该模块不属于此软件系统,应该从系统中剔除。如果所有模块之间都没有任何耦合关系,其必然导致一个结果:整个软件不过是多个互不相干的系统的简单堆积,对每个系统而言,所有功能还是要在一个模块中实现,相当于没有做任何模块的分解。
因此,模块之间必然会有这样或那样的依赖关系,永远不要幻想消除所有依赖,但是,过强的耦合关系(如,一个模块的变化,会造成一个或多个其他模块也同时发生变化的依赖关系),会对软件系统的质量造成很大的危害。特别是当需求发生变化时,代码的维护成本将非常高。多以,我们必须想尽办法来控制和消除不必要的耦合,特别是那些会导致其它模块发生不可控变化的依赖关系。
依赖倒置、控制反转、依赖注入等原则,就是人们在和依赖关系进行艰苦斗争的过程中不断产生和发展起来的。
接口和实现分离(Interface And Implement)
把接口和实现分开是人们试图控制依赖关系的第一个尝试。
Java语言提供了纯粹的接口类(Interface),这种接口类不包含任何实现代码,只是定义了要做什么功能,具体的实现代码写在实现该接口类的实现类(Implement)中。调用者只需要关心接口能做什么功能,而不用关心功能具体是怎么实现的。
interface PayInterface { // 定义接口
void payOnline(); // 定义一个抽象方法
} class PayImplement implements PayInterface { // 定义实现
void payOnline() { // 实现该接口的抽象方法
alipay();
}
void alipay() {
System.out.println("使用支付宝支付");
} void wechatPay() {
System.out.println("使用微信支付");
}
}
public class PayForEat {
public static void main(String[] args) {
Pay pay = new payImplement(); // 通过接口调用具体实现,实例化的是实现
pay.payOnline();
}
|
通过定义接口,可以把A模块对B模块的调用,从对具体实现的依赖转向对抽象接口的依赖。在上述例子中,A模块可以理解成PayForEat,B模块可以理解成PayImplement,加入接口Pay,相当于在A模块和B模块之间添加了一个第三方桥梁C,A模块和B模块的依赖关联从直接关联变成了第三方关联,A通过C依赖于B,即A依赖于C,而C依赖于B。这样的话,当B模块发生改变,即上述例子中的支付方式发生改变(比如从使用支付宝支付改变成使用微信支付),A模块并不需要做任何改动去适应B模块的改动。
接口和实现分离可以很好地隔离各个模块,从而尽量降低各个模块之间的耦合,为系统提供更好的可扩展性和可维护性。
接口体现的是规范和实现分离的设计哲学,让软件系统的各个组件之间面向接口耦合,是一种松耦合的设计。
依赖倒置(Dependency Inversion Principle)
依赖倒置原则是建立在抽象接口的基础上:
A:上层模块不应该依赖于下层模块,它们共同依赖于一个抽象。
B:抽象不能依赖于具象,而是具象依赖于抽象。
含义是,为了消除两个模块间的依赖关系,应该在两个模块之间定义一个抽象接口,上层模块调用抽象接口定义函数,下层模块实现该接口。
interface PayInterface { // 定义接口
void payOnline(); // 定义一个抽象方法
} class PayImplement implements PayInterface { // 定义实现
void payOnline() { // 实现该接口的抽象方法
// 调用支付接口
}
}
这里定义了一个抽象的接口PayInterface,其中有一个抽象的payOnline()方法,具体的实现在PayImplement实现类中,实现了payOnline()方法。当调用方调用payOnline()方法的时候,由于多态性机制的作用,实际调用的是具体的PayImplement实现类中的实现。因此,抽象接口隔离了调用方和提供方中的具体实现类,使它们之间没有直接的耦合关系,可以独立地扩展或重用。
例如我们可以另外定义一个AliPayImplement实现或WepayImplement,应用程序既可以根据需要选择支付宝支付或微信支付,甚至可以支付宝付一半,微信付一半。由此可以总结出,这种通过抽象接口消除调用方和提供方的实现之间依赖关系的做法具有以下特点:
1.调用方调用提供方的抽象接口,依赖于提供方的抽象接口;具体的实现类派生自提供方的抽象接口,也依赖于提供方的抽象接口。
2.调用方和具体的提供方的实现完全独立,相互之间没有直接的依赖关系,只要保持接口类的稳定,调用方和提供方的具体实现都可以独立地发生改变。
3.提供方完全可以独立重用,调用方可以和任何一个实现了相同抽象接口的提供方协同工作。
一般情况下,由于提供方的设计者并不知道调用方会如何使用提供的功能,抽象接口大多由提供方设计者根据自己设想的典型使用模式总结出来,并保留一定的灵活度,以提供给调用方的开发者使用。
看到这里就明白,依赖倒置的意思,就是说要程序依赖于抽象接口,而不是依赖于具体实现,把对具体实现的依赖转移到了抽象接口。简单的说,就是要求对抽象编程,不要对实现编程,这样就降低了客户与实现模块间的耦合。
控制反转(Inversion Of Controller)
控制反转从字面上来看,就是对控制权的反转。把创建对象的控制权交给第三方容器,当程序中需要用到实例对象的时候,就向第三方容器发出请求,由第三方容器返回一个实例对象。
依赖注入(Dependency Injection)
依赖注入是控制反转的具体实现。意思就是说当程序中需要用到实例对象的时候,才去向第三方容器发出请求,由第三方容器返回实例对象(注入)。
"忙碌且感恩的人,不会缺爱。"
java依赖的斗争:依赖倒置、控制反转和依赖注入的更多相关文章
- Java之控制反转和依赖注入
1.简介 依赖注入和控制反转,目的是为了使类与类之间解耦合,提高系统的可扩展性和可维护性,下面通过一个例子来引入这一概念. 2.案例 1)一般情况下的类耦合 Main.java public clas ...
- Java 控制反转和依赖注入模式【翻译】【整理】
Inversion of Control Containers and the Dependency Injection pattern --Martin Fowler 本文内容 Component ...
- java控制反转与依赖注入
1.简介 依赖注入和控制反转,目的是为了使类与类之间解耦合,提高系统的可扩展性和可维护性,下面通过一个例子来引入这一概念. 2.案例 1)一般情况下的类耦合 Main.java public clas ...
- Java Web实现IOC控制反转之依赖注入
控制反转(Inversion of Control,英文缩写为IoC)是一个重要的面向对象编程的法则来削减计算机程序的耦合问题,也是轻量级的Spring框架的核心. 控制反转一般分为两种类型,依赖注入 ...
- C#扫盲篇(二)依赖倒置•控制反转•依赖注入•面向接口编程--满腹经纶的说
扫盲系列的文章收到了广大粉丝朋友的支持,十分感谢,你们的支持就是我最大动力. 我的扫盲系列还会继续输出,本人也是一线码农,有什么问题大家可以一起讨论.也可以私信或者留言您想要了解的知识点,我们一起进步 ...
- 轻松了解Spring中的控制反转和依赖注入(二)
紧接上一篇文章<轻松了解Spring中的控制反转和依赖注入>讲解了SpringIOC和DI的基本概念,这篇文章我们模拟一下SpringIOC的工作机制,使我们更加深刻的理解其中的工作. 类 ...
- [.net 面向对象程序设计深入](26)实战设计模式——使用Ioc模式(控制反转或依赖注入)实现松散耦合设计(1)
[.net 面向对象程序设计深入](26)实战设计模式——使用IoC模式(控制反转或依赖注入)实现松散耦合设计(1) 1,关于IOC模式 先看一些名词含义: IOC: Inversion of con ...
- spring学习总结一----控制反转与依赖注入
spring作为java EE中使用最为广泛的框架,它的设计体现了很多设计模式中经典的原则和思想,所以,该框架的各种实现方法非常值得我们去研究,下面先对spring中最为重要的思想之一----控制反转 ...
- [.net 面向对象程序设计深入](31)实战设计模式——使用Ioc模式(控制反转或依赖注入)实现松散耦合设计(1)
[.net 面向对象程序设计深入](31)实战设计模式——使用IoC模式(控制反转或依赖注入)实现松散耦合设计(1) 1,关于IOC模式 先看一些名词含义: IOC: Inversion of con ...
随机推荐
- 让python bottle框架支持jquery ajax的RESTful风格的PUT和DELETE等请求(新方法)
通过上篇博文的方法处理后,进入代码调试后发现ajax获取不了服务器端返回的数据,度娘后发现原来AJAX的OPTIONS请求方式是状态类型查询,即向服务器提交信息后不返回任何信息,只将执行状态(200状 ...
- 设计模式のSingleton Pattern(单例模式)----创建模式
单例模式没有什么好讲的,我们 举个例子 #region 单例定义 /// <summary> /// 类单例 /// </summary> private static Win ...
- jvm运行时内存模式
jvm内存模型 内存模型粗略划分为:堆和栈 详细划分为:堆,虚拟机栈,方法区,本地方法区,程序计数器 程序计数器: 为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程 ...
- vue 数据绑定实现的核心 Object.defineProperty()
vue深入响应式原理 现在是时候深入一下了!Vue 最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是普通的 JavaScript 对象.而当你修改它们时,视图会进行更新.这使得状态管理非常简 ...
- UVA1471-Copying Books(二分答案)
Problem UVA1471-Copying Books Accept: 2669 Submit: 22797Time Limit: 3000 mSec Problem Description B ...
- 二.css介绍
一.三种引入样式1.内嵌样式:写在html中 style标签里面2.行内样式:写在具体的标签的style属性3.引入外部样式表:可以将样式规则写在外部文件,再引入到html中 <link typ ...
- 吴恩达课后作业学习2-week3-tensorflow learning-1-基本概念
参考:https://blog.csdn.net/u013733326/article/details/79971488 希望大家直接到上面的网址去查看代码,下面是本人的笔记 到目前为止,我们一直在 ...
- 使用TTS实现Oracle跨版本迁移
TTS实现数据库迁移,具有速度快.支持跨平台和跨版本等优点.本文记录了用TTS从10g single迁移到11g RAC的过程. Source数据库版本和字符集设置: SQL> select * ...
- Settings-Sync插件源码阅读
一.介绍 请参考官网: https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync 二.源码目录详解 Ima ...
- Ubuntu使用小技巧
1. Ubuntu下自由截图 Ubuntu下使用PrintScreen按键可以截取整个屏幕,但是很多时候并不需要那么多内容,还需要对图片进行编辑. 这时候就需要截图时,有矩形选择,更符合要求. 进入S ...