【问题标题】:What can AOP do that OOP can't do?AOP 能做什么而 OOP 不能做什么?
【发布时间】:2011-08-23 05:16:10
【问题描述】:

我主要是一名 Java 开发人员。我遇到了很多喜欢 AOP 的 Java 开发人员。我还看到最近出现的越来越多的 AOP“设计模式”似乎被广泛采用。尽管如此,出于几个原因,我仍然不相信 OO 代码中的 AOP 是一个好主意。

  1. 它在代码中添加了“魔法” 不透明的复杂性形式,可以 非常难以调试,并且可以 使调试变得非常困难 它的面向对象代码 影响。

  2. 在我看来主要是 不必要的,并且(更糟)经常使用 避免必须精心设计,或 弥补以前的贫穷 设计。

这是我在过去几年中经常看到的一个示例,作为我的问题的背景。

AOP 之前(来自 Hibernate 文档)

public void saveMyEntityToTheDatabase(MyEntity entity) {
    EntityTransaction tx = null;
    try {
        tx = entityManager.getTransaction();
        tx.begin();
        entityManager.persist(entity);
        tx.commit();
    } catch (RuntimeException e) {
        if(tx != null && tx.isActive()) {
            tx.rollback();
        }
        throw e;
    }
}

AOP 之后

@Transactional
public void saveMyEntityToTheDatabase(MyEntity entity) {
    entityManager.persist(entity);
}

对于很多人来说,这似乎是 AOP 的明显胜利。对我来说,最初的问题是 API 抽象级别不一致的症状。也就是说,EntityManager 比使用它的消息的业务级 API 低很多。这个问题可以通过更合适的抽象级别和更好的(OO)设计来解决。

OO 解决方案

public void saveMyEntityToTheDatabase(MyEntity entity) {
    database.performInTransaction(new Save(entity));
}

此解决方案假定database 对象包含与负责管理@Transactional 方法的切面相同类型的事务逻辑。这解决了我上面的担忧,更明显的是,有一些东西可以管理与 EntityManager 的交互,而不是引入另一个编程范式。

最后,我的问题是:AOP 能做什么而 OOP 不能?我稍微相信它在跟踪日志记录中的用处,也许默认 toString() 实现或类似的东西,但我很想知道是否有人发现它在特定类型方面明显优于 OO的问题。

【问题讨论】:

  • AoP,通过字节码修改,例如可以透明地将代码添加到(例如)第三方库,而无需修改源代码。
  • @Johan, ...这使得 AOP 编译器成为一个出色的黑客工具。
  • @weekens,确切地说,它不是 OOP 的替代品,而是一种应用 OOP 的便捷方式,有时是通过黑客攻击。
  • @Johan,这实际上是一个非常好的主意:听起来它可能比维护开源库的一部分直到修复提交要干净得多。 +1

标签: java oop aop


【解决方案1】:

AOP 是面向对象的;方面对象。

我不明白为什么是非此即彼的心态。

AOP 是链式横切关注点(例如日志记录、安全性、事务、远程代理等)的完美选择

更新:

我认为 OP 提出的批评是主观的,并不像所说的那样普遍。没有证据的断言可以在没有证据的情况下被驳回。

我不相信使用魔法,但如果你了解 AOP,它就不是魔法。我明白。也许OP没有。如果是这种情况,并且 OP 对 OO 解决方案更满意,我会说去吧。

“在我看来是不必要的”只是一种意见,没有证据。除了“我不同意”之外,没有其他答案。

我认为 AOP 非常适合这些情况,因为我可以以 声明性 的方式应用它。我可以编写一个方面类,然后通过细粒度控制将其应用到许多地方,在配置而不是代码中更改它。我可以选择哪些方法、类和包在配置中应用了一个方面。

尝试使用手写的 OO 方法。

此外,AOP 面向对象的。您可以将其视为一个聪明的人,为您提供特定领域的语言或框架来完成您想要手动完成的工作。共同特征已被抽象为更一般的东西。为什么会有人反对?

【讨论】:

  • AOP 是 OO 吗?不能在 OOP 范围之外使用 AOP?例如在函数式编程中?
  • 我的经验是方面装饰对象。如果您的函数式语言将函数视为一等对象,那么这是可能的。 Python 是一个混合体:函数式/面向对象。它的装饰器可以应用于函数或对象方法。
  • 我实际上并不觉得以这种方式查看 AOP 很有用:对我来说这似乎是一个完全不同的范例。你有什么理由说这是“完美”的选择(关于问题中的两个批评)?
  • 对 AOP 的主要问题 - 似乎与 OP 有一些共同点 - 基本上,它涉及“远距离的幽灵行动”甚至更多哎呀。 OOP 的“远距离诡异动作”至少没有COME FROM 风格的行为。我不能对 AOP 说同样的话,至少在 AspectJ 中是这样的。
  • “AOP is OO”和“Aspects are objects”是完全不正确和乏味的。AOP 的显着特征是通过添加运行代码的外部钩子(切入点)无形地改变代码的行为(建议). 这些都与 OOP 无关。AOP 和 OOP 是兼容但完全不同的概念 - 这个答案是完全不正确的。
【解决方案2】:

对我来说,AOP 是Interceptor Pattern 的简写。而拦截器模式本身就是从Template Method,AFAICS 派生(或影响或得到这个想法)的。

Interceptor 的一个流行示例是Servlet Filter。我们知道这些在很多情况下都非常有用。

由于所有这些模式都是有用的,因此从这些模式派生的 AOP 也很有用。正如您自己所说的那样,它的用途很少。

【讨论】:

  • 公平点。我对拦截器模式不太在意:我认为 servlet 过滤器非常有用。例如,如果 servlet 生命周期作为我正在开发的应用程序的一部分会发生变化,那么我就不那么喜欢它了。 AOP 有这样的危险。除了非常高级或非常低级的不变功能之外,我会非常犹豫将其用于任何其他用途。
【解决方案3】:

一般来说,所有形式的问题都是“不能做什么?”是没有意义的。所有通用语言都同样强大(参见:Church's Thesis)。

因此,语言之间的区别不在于它们能做什么,而在于如何 做。换句话说,你需要做多少工作才能在一种语言中获得某种行为,而在另一种语言中获得同样的行为需要做多少工作。

【讨论】:

  • 这对我来说似乎是一个非常短期的观点。以我的经验,预先输入的字符较少并不能转化为“您必须做多少工作才能获得某些行为”。可维护性可以更好地预测代码花费的时间。
  • 我没有说你应该计算#characters。在您喜欢的任何时间段内采用您喜欢的任何努力/成本定义。我的观点是所有语言都是平等的。
【解决方案4】:

到目前为止,对我而言,唯一能明确优于 OOP 的用例是方法调用跟踪。

【讨论】:

  • 这也是我的经验。我学习 AspectJ 只是为了实现它!
【解决方案5】:

Aspect Oriented Programming vs. Object-Oriented Programming

Mecki 关于 AOP 的回答是独一无二的 ;-)

【讨论】:

    【解决方案6】:

    简短的回答是……什么都没有。虽然 AOP 增加了我们在美国海军陆战队时期称为 FM 的一小部分,当为平民观众清理时,这意味着“Freaking Magic”。你是对的,在你引用的第一个案例中绝对没有任何成就,而在第二个案例中没有取得任何成就。我认为该运动背后的主要原因是清晰度问题,以及代码中“少仪式”的日益增长的口头禅。因此,您可以编写代码来处理事务,或者使用由供应商提供的 AOP 来免除仪式,或者容器可能比您手动编写的代码经过更好的测试。 AOP 的另一优点是它可以在部署描述符、Spring 配置文件等中进行更改,然后可以在您的需求发生更改时进行更改,而无需对实际代码进行任何更改。因此,您手写的昂贵代码继续表达您打算付费执行的业务逻辑,并且“FM”层处理事务、日志记录等事务,并带有大量 AOP 精灵粉。

    当然是 YMMV。

    【讨论】:

    • 我喜欢这个答案。您对供应商开发的方面提出了一个很好的观点,我倾向于认为我只会将 AOP 用于需要神奇的事情。不过在这方面,我很满意Muggle
    【解决方案7】:

    我很不幸不得不在一个使用普通 OOP 完成服务调用的应用程序中工作。我讨厌它,我会告诉你原因:

    首先,您的示例有些简单,因为通常事务边界不是围绕单个数据库交互,而是围绕整个业务服务调用。因此,让我们为了示例考虑以下方法:

    @Transactional
    public Employee hire(Person p, Employee manager, BigDecimal salary) {
        // implementation omitted
    }
    

    使用

    调用
    Employee hired = service.hire(candidate, manager, agreedOnSalary);
    

    在你的情况下,这将变成:

    class HireAction implements Callable<Employee> {
        private final Person person;
        private final Employee manager;
        private final BigDecimal salary;
    
        public HireAction(Person person, Employee manager, BigDecimal salary) {
            this.person = person;
            this.manager = manager;
            this.salary = salary;
        }
    
        @Override
        public Employee call() throws Exception {
            // implementation omitted
        }
    }
    

    并被调用

    Employee hired = session.doInTransaction(new HireAction(candidate, manager, agreedOnSalary));
    

    构造函数是确保所有参数都被赋值的必要条件(因此如果一个参数被添加到一个方法中而不更新调用者,编译器会报错)。

    第二种方法较差,原因如下:

    1. 它违反了 DRY,因为每个参数被提及 3 次,返回类型被提及两次。特别是,您将参数的 JavaDoc 放在哪里?你也复制那个吗?
    2. 很难将相关的服务操作分组到一个服务中,并在它们之间共享代码。是的,您可以将所有相关操作放在同一个包中,并有一个公共超类来共享代码,但这又比简单地将它们放在同一个类中的 AOP 方法更加冗长。或者你可以做一些疯狂的事情,比如:

      class HRService {
          class HireAction {
              // impl omitted
          }
          class FireAction {
              // impl omitted
          }
      }
      

      并使用

      调用它
      Employee emp = session.doInTransaction(new HRService().new HireAction(candidate, manager, salary));
      
    3. 应用程序员可能忘记启动事务:

      Employee e = new HireAction(candidate, manager, salary).call();
      

      或在错误的会话/数据库上启动事务。事务和业务逻辑是不同的关注点,通常由不同的开发人员解决,因此应该分开。

    总而言之,简单的 OOP 方法更加冗长且容易出错,从而导致开发和维护期间的成本增加。

    最后,关于您对 AOP 的批评:

    它以难以调试的不透明复杂性的形式为代码添加了“魔力”,

    无论来源如何,复杂性总是难以调试。我记得一些使用 hibernate 源代码的调试会话,它们对命令模式的明智使用使得找到重要的代码同样困难。

    AOP 拦截器的存在可能是不明显的(尽管如果一个方法被注解 @Transactional,我认为这很明显),这就是为什么应该谨慎使用 AOP(这不是问题的数量)项目中的横切关注点通常很小)。

    并且会使调试受它影响的面向对象代码变得极其困难。

    怎么会?我没有看到问题,但如果你要描述它,我可能会告诉你我是如何解决/避免它的。

    在我看来,这几乎是不必要的,而且(更糟糕的是)通常用于避免设计得很好,或者弥补以前糟糕的设计。

    每一种技术都可能用得不好。重要的是用好有多难,用好能达到什么效果。

    【讨论】:

    • 我同意:服务层 API 的示例失败。我的意图只是描述一种简单化的方法。我认为你对 Hibernate 和糟糕的设计提出了一些好的观点。
    猜你喜欢
    • 2018-05-30
    • 2016-11-20
    • 2010-11-24
    • 2016-05-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-17
    • 1970-01-01
    相关资源
    最近更新 更多