面向切面编程与面向对象编程的比较

230

和大多数开发人员一样,我在这里和整个世界上都使用面向对象编程(OOP)技术开发软件系统多年。因此,当我读到面向切面编程(AOP)可以解决传统OOP无法完全或直接解决的许多问题时,我会停下来思考,这是真的吗?

我阅读了很多信息,试图学习这种AOP范式的关键,并且我仍然处于同一位置,因此,我想更好地了解它在现实世界应用开发中的好处。

有人有答案吗?


16
所有回答都非常好,这是一个完美的案例,适合单个社区编辑的回答,将它们合并在一起。所有人都在用不同的方式和例子表达相同的意思,并增加了总体价值。 - Vinko Vrsalovic
7个回答

373
我为什么要使用"vs"这个词呢?它不是"vs"。你可以将面向方面的编程与函数式编程相结合,也可以将其与面向对象编程相结合。它不是"vs",而是"面向方面编程面向对象编程"。
对我来说,AOP 是一种"元编程"。AOP 所做的一切都可以通过添加更多代码来完成。AOP 只是节省了你编写此类代码的时间。
维基百科有一个最好的元编程示例。假设您有一个具有许多"set...()"方法的图形类。每次调用 set 方法后,图形数据都会发生变化,因此图形也会发生变化,因此需要在屏幕上更新图形。假设要重新绘制图形,必须调用"Display.update()"。传统的方法是通过添加更多代码来解决这个问题。在每个 set 方法的末尾,您编写
void set...(...) {
    :
    :
    Display.update();
}

如果你有三个设定方法,那没有问题。如果你有200个(假象的),那么在每个地方都添加这个方法将变得非常痛苦。此外,每当你添加一个新的设定方法时,你必须确保不要忘记将其添加到末尾,否则你就会创建一个错误。
AOP解决了这个问题,而不需要添加大量代码,相反,你只需添加一个方面:
after() : set() {
   Display.update();
}

就是这样!你不需要自己编写更新代码,只需告诉系统在达到set()切入点后,必须运行此代码,它就会运行此代码。无需更新200个方法,也无需确保在新的set方法中不要忘记添加此代码。此外,您只需要一个切入点:

pointcut set() : execution(* set*(*) ) && this(MyGraphicsClass) && within(com.company.*);

这是什么意思?这意味着如果一个方法被命名为"set*"(*表示任何名称可能在set后面跟随),无论该方法返回什么(第一个星号)或者需要什么参数(第三个星号)且它是MyGraphicsClass的方法,而且这个类属于"com.company.*"包,则这是一个set()切入点。我们的第一段代码说:"在运行任何是set切入点的方法之后,运行以下代码"。
看到AOP如何优雅地解决了这个问题了吗?实际上,这里描述的一切都可以在编译时完成。AOP预处理器可以在编译类本身之前修改您的源代码(例如,在每个set-pointcut方法的末尾添加Display.update())。
然而,这个例子也展示了AOP的一个重要缺点。AOP实际上正在做许多程序员认为是"反模式"的事情。确切的模式称为"远程操作"。

远程操作是一个反模式(一个公认的常见错误),其中程序的某一部分的行为会根据另一部分的难以或不可能识别的操作而大幅度变化。

作为一个项目的新手,我可能会读任何set-method的代码并认为它已经损坏了,因为它似乎没有更新显示。通过查看set-method的代码,我看不到在执行后,将“神奇地”执行其他代码以更新显示。我认为这是一个严重的缺点!通过更改方法,可能会引入奇怪的错误。进一步理解代码流程,在某些东西似乎正常工作但不明显(就像我说的那样,它们只是以某种方式神奇地工作...),真的很难。
更新
只是为了澄清:有些人可能会觉得我在说AOP是一些坏东西,不应该使用。那不是我想说的! AOP实际上是一个很棒的功能。我只是说“小心使用”。如果您混淆普通代码和相同Aspect的AOP,则AOP将只会导致问题。在上面的示例中,我们有更新图形对象值和绘制更新对象的Aspect。实际上,这是一个单一的Aspect。将其一半编码为普通代码,另一半编码为Aspect会增加问题。
如果您将AOP用于完全不同的方面,例如用于日志记录,那么您就不会遇到反模式问题。在这种情况下,项目的新手可能会想“所有这些日志消息从哪里来?我在代码中看不到任何日志输出”,但这并不是一个很大的问题。他对程序逻辑所做的更改几乎不会破坏日志功能,对日志功能所做的更改也几乎不会破坏他的程序逻辑 - 这些方面是完全分开的。使用AOP进行日志记录的好处在于,您的程序代码可以完全集中精力做它应该做的事情,而您仍然可以具有复杂的日志记录,而不会让您的代码被无数的日志信息所淹没。此外,当引入新代码时,神奇的日志消息将在正确的时间以正确的内容出现。新手程序员可能不理解它们为什么存在或者它们来自哪里,但由于它们会在“正确的时间”记录“正确的事情”,他只需要欣然接受它们的存在,并转向其他事情。

因此,在我的示例中,AOP的良好用法是始终记录是否通过set方法更新了任何值。这不会创建反模式,也几乎不会导致任何问题。

有人可能会说,如果您可以轻松滥用AOP来创建如此多的问题,那么使用它是一个坏主意。然而,哪种技术不能被滥用?您可以滥用数据封装,您可以滥用继承。几乎每个有用的编程技术都可能被滥用。考虑一种编程语言,它非常有限,只包含无法被滥用的功能;一种只能按照最初设计的方式使用功能的语言。这种语言非常有限,甚至可以争论它是否能用于实际编程。


7
记录日志似乎是一个AOP不会导致远程作用的具体示例。目前,维基百科提供了使用切面进行安全检查等方面的示例,这确实使程序流程更加易于理解。 - kizzx2
9
@kizzx2:关于记录日志的观点非常好,事实上——这是我到目前为止看到最好的展现AOP优势的例子,尽管我对AOP不是特别了解。感谢分享! - blunders
5
@Pacerier 我举的例子比较简化,因为SO不是一个教学论坛。我只是回答提问者的问题,可能比必要的更详细。如果你想了解更多关于AOP的内容,可以阅读一些程序员文档,如果有具体的问题,为什么不在这里发起一个新问题呢?不要在评论中提问,去创建一个新问题,因为SO就是为此而存在的。我相信有人会在回复中帮你解决疑惑的。 - Mecki
3
@Pacerier 抱歉,我无法理解你的观点。请参见此处:https://dev59.com/9Gox5IYBdhLWcg3w_ZR0#8843713 此代码记录了每个公共方法的每次调用,包括所有方法参数类型和值。您只需编写一次,不需要为任何方法添加零样板代码,它就是答案中显示的代码。 - Mecki
1
很好的解释,我同意在代码中无法立即看到执行情况是一个非常大的缺点。这不像是非常清晰明了的。我更喜欢看到Display.update()而不是思考类似于:“显示正在更新,但在哪里?”如果我知道它使用AOP,我可能还需要搜索我所看到的代码被建议的位置,对吗?我相信IDE可以帮助,但我仍然认为看到Display.update()更好。有一种叫做模板方法的模式可以解决这种问题,对吗? - Fernando Gabrieli
显示剩余6条评论

38

面向切面编程提供了一种不错的方式来实现跨越多个领域的关注点,如记录日志和安全性。

这些关注点是逻辑片段,需要在许多地方应用,但实际上与业务逻辑没有任何关系。

您不应将AOP视为OOP的替代品,而应该将其视为一个不错的附加组件,可以使您的代码更加清晰、松耦合并专注于业务逻辑。

因此,通过应用AOP,您将获得两个主要优点:

  1. 每个关注点的逻辑现在都在一个地方,而不是分散在整个代码库中。

  2. 类更加清洁,因为它们只包含主要关注点(或核心功能)的代码,次要关注点已移动到切面中。


32

OOP和AOP并不是互相排斥的。 将AOP添加到OOP中可能是一个很好的补充。 AOP非常方便地为方法添加标准代码,例如记录日志、跟踪性能等,而无需将这些标准代码混杂在方法代码中。


13

我认为这个问题没有普遍的答案,但需要注意的一点是,AOP并不是取代OOP,而是添加了某些分解功能,解决了所谓的“主导组合的暴政”(或横切关注点) (1)。

只要你掌控特定项目使用的工具和语言,并且理解方面之间相互作用以及需要额外工具(如AJDT)来理解程序,它肯定在某些情况下有所帮助,但也增加了新的复杂性。

Gregor Kiczales曾在Google Tech Talks上发表过一次有趣的AOP简介演讲,我建议您观看:Aspect Oriented Programming: Radical Research in Modularity


9

OOP主要用于组织您的业务逻辑,而AOP则有助于组织非功能性事物,例如审核、日志记录、事务管理、安全等。

通过这种方式,您可以将业务逻辑与非功能性逻辑解耦,使代码更加清晰。

另一个优点是,您可以非常一致地应用建议(如审核),而无需实现任何接口,这为修改提供了极大的灵活性,而不会触及业务逻辑。

这样就容易实现关注点分离单一职责

此外,当仅解决业务问题时,很容易从一个框架(比如Spring)移植业务逻辑到其他地方。


9
首先,AOP不会取代OOP,而是扩展了OOP。OOP的思想和实践仍然很重要。有一个良好的对象设计可能会使它更容易通过方面进行扩展。
我认为AOP带来的思想很重要。我们需要找出在不改变类本身的情况下实现跨类程序中横切关注点的方法。但我认为AOP最终将成为我们使用的其他工具的一部分,而不是单独的工具或技术。我们已经看到这种情况发生了。
像Ruby和Python这样的一些动态语言具有诸如mixins之类的语言结构,可以解决相同的问题。这看起来很像AOP,但与语言更好地集成。
Spring、Castle和其他一些依赖注入框架提供了向注入的类添加行为的选项。这是一种运行时编织的方法,我认为这具有很大的潜力。
我认为你不必学习完全新的范例来使用AOP。这些思想很有趣,但正在逐渐被现有的工具和语言吸收。只需保持了解并尝试这些工具即可。

1

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