我主要是一名Java开发人员。我遇到了很多喜欢AOP的Java开发者。我也看到越来越多的AOP“设计模式”最近出现,似乎被广泛采用。即便如此,由于几个原因,我仍然不相信OO代码中的AOP一般是个好主意。
它为代码添加了“魔力” 形式的不透明的复杂性,可以 非常难以调试,而且可以 使调试非常困难 它面向对象的代码 影响。
在我看来,大多数情况下 不必要的,并且(更糟)经常使用 避免设计好,或 以弥补以前的穷人 设计。
这是我在过去几年中经常看到的一个例子,作为我的问题的背景。
在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明显更好特殊类型的问题。
答案 0 :(得分:12)
AOP是OO;方面是对象。
我不明白为什么要么/或心态。
AOP是链接,跨领域关注点(例如日志记录,安全性,事务,远程代理等)的完美选择。
更新:
我认为OP所提出的批评是主观的,并不像所说的普遍普及。没有证据的断言可以在没有证据的情况下被驳回。
我不相信使用魔法,但如果你了解它,AOP并不神奇。我明白了。也许OP没有。如果是这种情况,并且OP对OO解决方案更为满意,那我就说去吧。
“似乎对我来说是不必要的”仅仅是一种意见,没有证据。除了“我不同意”之外,没有答案。
我认为AOP非常适合这些情况,因为我可以用声明式方式应用它。我可以编写一个方面类,并在许多地方应用细粒度控制,在配置而不是代码中更改它。我可以选择哪些方法,类和包在配置中应用了一个方面。
尝试用手写的OO方法。
此外,AOP 面向对象。您可以将其视为一个聪明的人,为您提供您想要手动完成的特定领域语言或框架。共同特征被抽象为更一般的东西。为什么有人会反对呢?
答案 1 :(得分:10)
简短的回答是......什么都没有。 AOP虽然增加了我在美国海军陆战队作为FM的日子里所提到的一些内容,当为平民观众清理时意味着“怪异的魔法”。你是对的,在你引用的第一种情况下,第二种情况并没有实现。我认为这一运动背后的主要原因是一个清晰的问题,以及代码中“少礼”的颂歌。因此,您可以编写代码来处理交易,或者免除由供应商提供的AOP的仪式,或者容器可能比您手动编写的代码更好地测试。 AOP有利的另一点是它可以在部署描述符,Spring配置文件等中进行更改,然后如果您的需求发生更改而不更改实际代码,则可以更改它。因此,您手写昂贵的代码将继续表达您需要支付的业务逻辑,并且“FM”层处理交易,日志记录等事务,并自由地使用AOP小精灵粉尘。
YMMV当然。
答案 2 :(得分:8)
对我而言AOP是Interceptor Pattern的简写。拦截器模式本身是从Template Method,AFAICS派生(或影响或得到了想法)。
Interceptor
的一个常见示例是Servlet Filter
。我们知道这些在很多情况下非常有用。
因为所有这些模式都是有用的,因此从这些模式衍生的AOP也是有用的。正如你自己所说的那样,它的用法很少。
答案 3 :(得分:6)
一般来说,所有形式的问题“什么不能做?”毫无意义。所有通用语言都同样强大(参见:Church's Thesis)。
因此,语言之间的区别不在于他们可以做什么,而在于 他们是如何做到的。换句话说,你需要做多少工作才能在一种语言中获得一些行为,而在另一种语言中获得相同行为需要多少工作。
答案 4 :(得分:2)
Aspect Oriented Programming vs. Object-Oriented Programming
Mecki关于AOP的答案是独家的; - )
答案 5 :(得分:1)
对我来说,到目前为止,唯一一个用例,其中绝对优于OOP的是方法调用跟踪。
答案 6 :(得分:1)
我不幸在一个使用普通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));
构造函数是确保分配所有参数所必需的(因此,如果在不更新调用者的情况下将参数添加到方法,编译器可能会抱怨)。
第二种方法较差,原因如下:
很难在服务中对相关服务操作进行分组,并在它们之间共享代码。是的,你可以将所有相关的操作放在同一个包中,并且有一个共同的超类来共享代码,但是再次说明AOP方法只是简单地将它们放在同一个类中就更加冗长了。或者你可以做一些疯狂的事情:
class HRService {
class HireAction {
// impl omitted
}
class FireAction {
// impl omitted
}
}
并使用
调用它Employee emp = session.doInTransaction(new HRService().new HireAction(candidate, manager, salary));
应用程序员可能忘记启动事务:
Employee e = new HireAction(candidate, manager, salary).call();
或在错误的会话/数据库上启动事务。事务和业务逻辑是不同的问题,通常由不同的开发人员解决,因此应该分开。
总而言之,简单的OOP方法更加冗长且容易出错,导致开发和维护期间的成本增加。
最后,关于你对AOP的批评:
它以不透明的复杂性形式为代码添加了“魔力”,这可能非常难以调试,
无论原点如何,复杂性始终难以调试。我记得一些使用hibernate源代码的调试会话,他们明智地使用命令模式使得找到重要的代码同样困难。
AOP拦截器的存在可能是不明显的(虽然如果一个方法注释@Transactional
,我认为这很明显),这就是为什么应该谨慎使用AOP(这不是问题,因为项目中贯穿各领域的问题通常很少。)
并且可能使调试它所影响的面向对象的代码变得非常困难。
怎么样?我没有看到问题,但如果你要描述它,我可能会告诉你我是如何解决/避免的。
在我看来,大多数情况下都是不必要的,并且(更糟糕的是)经常习惯于避免设计好,或者补偿之前糟糕的设计。
每种技术都可以使用得很差。重要的是如何使用得好,以及如果我们这样做,我们可以做些什么。