AOP是什么,依赖注入和控制反转是什么?

41
我尝试理解AOP、依赖注入和控制反转这些与SPRING相关的概念,但是我很难理解。是否有人可以用简单易懂的英语来解释一下?
6个回答

54
我理解你的困惑,我自己也花了一些时间来理解这些概念是如何相互关联的。以下是我(有点个人化的)对这一切的解释:
1. 控制反转 控制反转是一个相当通用的设计原则,它指的是将行为的规范与其实际执行时刻分离。例如进行比较,
myDependency.doThis();

使用

myDependency.onEventX += doThis();

在后者中,没有直接调用,这使得它更加灵活。通常情况下,控制反转与观察者模式、事件或回调有关。
2. 依赖倒置是另一个设计原则。粗略地说,它表示高级抽象不应该直接依赖于低级抽象;否则就会导致高级抽象不能在没有低级抽象的情况下被重复使用。
 class MyHighLevelClass {
     MyLowLevelClass dep = new MyLowLeverClass();
 }

 class App {
     void main() {  new HighLevelClass().doStuff(); }
 }

在这里,如果没有访问MyLowLevelClass,MyHighLevelClass无法编译。为了打破这种耦合,我们需要使用接口对低级类进行抽象,并删除直接实例化。
class MyLowLevelClass implements MyUsefulAbstraction { ... }

class MyHighLevelClass {

    MyUsefulAbstraction dep;

    MyHighLevelClass( MyUsefulAbstraction dep ) {
        this.dep = dep;
    }
}

class App {
     void main() {  new HighLevelClass( new LowLevelClass() ).doStuff(); }
 }

请注意,您不需要像容器一样特别的东西来执行依赖反转(Dependency Inversion)原则。推荐阅读《依赖反转原则》by Uncle Bob,以了解更多相关内容。
3. 依赖注入
现在让我们来谈谈依赖注入。对我而言,依赖注入 = IoC + 依赖反转
1. 外部提供依赖项,因此我们强制执行依赖反转原则。 2. 容器设置依赖项(而不是我们),因此我们谈论控制反转。
在我提供的示例中,如果使用容器来实例化对象并自动将依赖项注入构造函数中,则可以进行依赖注入(这时我们经常谈论 DI 容器)。
 class App {
     void main() {  DI.getHighLevelObject().doStuff(); }
 }

请注意,有多种注入形式。同时,在这个角度下,可以将setter注入视为回调的一种形式——DI容器创建对象然后回调setter。控制流被有效地反转。
4. AOP
严格来说,AOP与前面三点关系不大。关于AOP的开创性论文非常通用,提出了将各种来源(可能使用不同语言表达)编织在一起以生成工作软件的想法。
我不会更多地展开关于AOP的内容。这里重要的是,依赖注入和AOP确实很好地协作,因为它使编织变得非常容易。如果使用IoC容器和依赖注入来抽象化对象的实例化,则IoC容器可以很容易地用于在注入依赖项之前编织方面。否则,这将需要特殊的编译或特殊的ClassLoader
希望这有所帮助。

这对我有所帮助,但您能否更详细地阐述依赖反转并与控制反转进行比较,我无法理解这两个概念。非常感谢提供有用的示例以理解这些概念。 - Rachel

10

依赖注入在如何向5岁儿童解释依赖注入?中得到了很好的解释:

当你自己去冰箱里拿东西时,你可能会引起问题。你可能会把门打开,你可能会拿一些爸爸妈妈不想让你拿的东西。你甚至可能在寻找我们没有的东西或过期的东西。

你应该做的是表达一个需求,“我需要午饭时喝点东西”,然后我们会确保你吃饭时有东西喝。

AOP - 面向切面编程 - 基本上意味着您编写的源代码会根据其他位于ELSEWHERE的规则进行修改。这意味着您可以说“对于每个方法的第一行,我希望在一个中央位置有一个'log.debug(“entering method()”)',然后您使用该规则编译的每个方法都将包含该行。 "Aspect" 是从第一行源代码以外的方式查看代码的名称。

控制反转基本上意味着您没有一个中心化的代码控制一切(例如在main()中的巨型开关),而是有很多“某种方式”调用的代码块。该主题在维基百科上进行了讨论:http://en.wikipedia.org/wiki/Inversion_of_control


2
下投票的原因可能是你的答案没有涉及这些概念之间的关系,而问题中正是询问这个难点。(虽然我不是那个下投票的人。) - ewernli

2
这三个概念都是不同的,但它们协同工作得很好,因此Spring应用程序通常同时使用它们。让我们举个例子。假设我们有一个可以执行许多不同操作的Web应用程序。我们可以以许多方式构建此应用程序,但一种方式是创建一个负责执行每个操作的类。我们需要从某个地方调用和创建这些类。其中一种选择是拥有一个大的主类,该类创建每个服务的实例,打开套接字,并在调用进来时将调用传递给这些服务。不幸的是,我们创建了一个过于复杂的类,它具有过多的逻辑并且对我们程序中的所有内容都了解得太多。如果我们更改程序中的任何内容,我们可能需要修改此类。此外,它很难测试。如果它直接实例化和调用其他类,则无法单独测试任何类。单元测试变得困难得多。解决这个问题的方法是使用控制反转。我们说“好的,这些是服务类。谁实例化它们?不是我。”通常,每个服务类定义一个接口,例如LoginService或BillingService。可能会有该接口的多个实现,但您的应用程序不关心。它只知道它可以请求某种类型的服务或具有特定名称的服务,然后会得到一些好东西。依赖注入允许我们将所有小部件连接在一起。类具有可访问的字段、构造函数参数或设置器方法,这些字段、参数或方法是对它们需要访问的其他组件的引用。这使得单元测试变得更加容易。您可以创建要测试的对象,将模拟或存根依赖项传递给它,然后测试该对象在隔离状态下是否正确行为。现在,我们的实际应用程序是一个复杂的混乱,所有这些部分都需要以恰当的方式连接在一起。有许多方法可以实现这一点,包括允许应用程序进行猜测(“这个类想要一个UserService,我负责的只有一个其他类实现了UserService”)或通过仔细地解释它们如何在XML或Java中连接在一起。Spring在其核心是一个服务,负责连接这些类。现在我们来看AOP。假设我们有所有这些类,它们以复杂的方式彼此连接。有一些横跨多个类的问题,我们可能希望以非常通用的方式描述这些问题。例如,也许您想在调用任何服务时启动数据库事务,并在服务不抛出异常的情况下提交该事务。结果发现,Spring能够执行此任务。Spring可以动态创建实现您的类所需接口的代理类,并将您的类包装在其代理中。现在,IoC和依赖注入并不是执行面向方面编程所必需的,但这是一种非常方便的方法来完成它。

我对依赖注入和控制反转的区别感到困惑,我一直以为这两个术语指的是同一件事情,一个是设计模式,另一个是实现方式,你能否纠正我的理解或提供更多的解释和示例? - Rachel
1
它们非常密切相关。依赖关系是一种控制反转的类型。有几种类型的事物可以使它们的控制反转,其中之一就是依赖管理。其他形式的控制反转包括,例如,让操作系统驱动用户界面小部件而不是应用程序本身。Martin Fowler在他创造“依赖注入”这个词组的文章中使用了这个例子:http://martinfowler.com/articles/injection.html#InversionOfControl - Brandon Yarbrough

2

依赖注入和控制反转的区别在这篇文章中有很好的解释。

("你是指依赖反转,对吗?"部分)

总结:

DI关注的是一个对象如何获取依赖。当依赖从外部提供时,系统使用DI。

IoC关注的是谁发起调用。如果你的代码发起了调用,则不是IoC;如果容器/系统/库回调到你提供的代码,则是IoC。


1
让我向您介绍一些AOP相关的词语,希望这能使它更容易理解。 AOP的基本原则是找到代码中多次出现且不属于具体业务的常见任务/方面。例如:每次进入任何函数时写入日志,或在创建对象时包装它,或在调用特定函数时向管理员发送电子邮件。因此,我们从程序员手中夺取这些非业务方面,并在幕后管理它们。这就是AOP的基础之一......

1

来自《Spring实战》的简单比较:

依赖注入(DI)帮助你将应用程序对象解耦,AOP则帮助你将横切关注点从它们所影响的对象中解耦。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接