I'm primarily a Java developer. I've met quite a few Java devs who love AOP. I've also been seeing more and more AOP "design patterns" emerging recently that seem to be fairly widely adopted. Even so, I'm still not convinced that AOP in OO code is a good idea in general, for a couple of reasons.
It adds "magic" to the code in the form of opaque complexity that can be extremely hard to debug, and can make it extremely hard to debug object oriented code that it affects.
It seems to me to be mostly unnecessary, and (worse) often used to avoid having to design well, or to compensate for previous poor design.
Here is an example that I've been seeing a lot of over the past couple of years, as a background for my question.
Before AOP (from the Hibernate docs)
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;
}
}
After AOP
@Transactional
public void saveMyEntityToTheDatabase(MyEntity entity) {
entityManager.persist(entity);
}
It seems like an obvious win for AOP to a lot of people. To me the original problem is symptomatic of inconsistent levels of API abstraction. That is, the EntityManager
is a lot lower level than the business-level API of the message using it. This problem can be solved with a more appropriate level of abstraction, and a better (OO) design.
An OO Solution
public void saveMyEntityToTheDatabase(MyEntity entity) {
database.performInTransaction(new Save(entity));
}
This solution assumes that the database
object contains the same kind of transactional logic that the aspect responsible that manages @Transactional
methods. This addresses my concerns above by making it more obvious that there is something managing the interaction with the EntityManager
, and not introducing another programmi开发者_Go百科ng paradigm.
So finally, my question: what can AOP do that OOP can't? I'm slightly convinced of its usefulness in trace logging, and maybe default toString()
implementations or something similar, but I'd love to know if anyone's found it to be significantly better than OO for particular types of problems.
AOP is OO; Aspects are objects.
I don't understand why the either/or mentality.
AOP is the perfect choice for chained, cross-cutting concerns (e.g. logging, security, transactions, remote proxying, etc.)
UPDATE:
I think the criticisms offered by the OP are subjective and not as universally widespread as stated. Assertions stated without proof can be dismissed without proof.
I don't believe in using magic, but AOP is not magic if you understand it. I understand it. Perhaps the OP does not. If that's the case, and the OP is more comfortable with an OO solution, I'd say go for it.
"Seems to me to be unnecessary" is a mere opinion, offered without proof. There's no answer to that except "I disagree."
I think AOP is perfect for those cases because I can apply it in a declarative way. I can write an aspect class once and apply it in many places with fine-grained control, changing it in configuration rather than code. I can pick and choose which methods, classes, and packages have an aspect applied to them in configuration.
Try that with a hand-written OO approach.
Besides, AOP is object-oriented. You can look at it as a smart person giving you a domain-specific language or framework for what you want to do by hand. The common features have been abstracted out into something more general. Why would anyone object to that?
The short answer is ... nothing. AOP though adds a pinch of what we would refer to back in my days in the US Marines as FM, which when cleaned up for a civilian audience means "Freaking Magic". You're right that there's absolutely nothing achieved in the first case you cite that isn't achieved in the second. I think the main reasoning behind the movement is a question of clarity, and the growing mantra of "Less ceremony" in code. So, you can write code in order to handle the transactions, or dispense with the ceremony with AOP, which being provided by the vendor, or container is presumably better tested than the code you are writing by hand. Another point in AOP's favor is that it CAN be changed in the deployment descriptors, Spring configuration files etc, and can then be altered if your requirements change without any changes to your actual code. So you hand written expensive code continues to express the business logic you meant are paid to do, and the "FM" layer handles things like transactions, logging, etc with a liberal sprinkling of AOP pixie dust.
YMMV of course.
To me AOP is a shorthand of Interceptor Pattern. And Interceptor Pattern itself is derived (or influenced or got the idea) from Template Method, AFAICS.
A popular example of Interceptor
is Servlet Filter
. And we know those are pretty useful in many cases.
Since, all these patterns are useful, hence AOP, which is derived from these is also useful. And as you yourself stated few of its usages.
In general, all question of the form "What can do that cant?" are meaningless. All general purpose languages are equally powerful (See: Church's Thesis).
The difference between languages is, therefore, not in what they can do but rather in how they do it. In other words, how much work do you have to do get some behavior in one language, vs. how much work to get that same behavior in some other language.
Aspect Oriented Programming vs. Object-Oriented Programming
The answer of Mecki about AOP is an exclusive one ;-)
For me so far, the only use case, for which AOP is definetely better than OOP is method call tracing.
I have had the misfortune of having to work in an application where service calls were done using plain OOP. I hated it, and I am going to tell you why:
First, your example is somewhat simplistic, as usually the transaction boundary is not around a single database interaction, but around the entire business service invocation. So let's for the sake of the example consider the following method:
@Transactional
public Employee hire(Person p, Employee manager, BigDecimal salary) {
// implementation omitted
}
which is invoked using
Employee hired = service.hire(candidate, manager, agreedOnSalary);
In your case, this would become:
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
}
}
and invoked by
Employee hired = session.doInTransaction(new HireAction(candidate, manager, agreedOnSalary));
The constructor is necessary to ensure that all parameters are assigned (so the compiler can complain if a parameter is added to a method without updating a caller).
The second approach is inferior for the following reasons:
- It violates DRY, as each parameter is mentioned 3 times, and the return type twice. In particular, where do you put JavaDoc for the parameters? Do you copy that, too?
It makes it hard to group related service operations in a service, and share code among them. Yes, you can put all related operations in the same package, and have a common superclass to share code, but again this is rather more verbose that the AOP approach of simply putting them in the same class. Or you could do crazy things like:
class HRService { class HireAction { // impl omitted } class FireAction { // impl omitted } }
and invoke it using
Employee emp = session.doInTransaction(new HRService().new HireAction(candidate, manager, salary));
An application programmer can forget to start the transaction:
Employee e = new HireAction(candidate, manager, salary).call();
or start the transaction on the wrong session / database. Transactions and business logic are different concerns, usually solved by different developers, and hence should be separated.
To summarize, the plain OOP approach is more verbose and error prone, leading to increased costs during both development and maintenance.
Finally, about your critism of AOP:
It adds "magic" to the code in the form of opaque complexity that can be extremely hard to debug,
Complexity is always hard to debug, regardless of origin. I remember some debugging sessions with the hibernate source code, whose judicious use of the command pattern made it no less hard to find the code that mattered.
The presence of an AOP interceptor can be non-obvious (though if a method is annotated @Transactional
, I'd consider it obvious), which is why AOP should be used sparingly (which is not a problem as the number of cross-cutting concerns in a project is usually quite small).
and can make it extremely hard to debug object oriented code that it affects.
How so? I don't see the problem, but if you were to describe it, I could probably tell you how I solve / avoid it.
It seems to me to be mostly unnecessary, and (worse) often used to avoid having to design well, or to compensate for previous poor design.
Every technology can be used poorly. What matters is how hard it is to use well, and what we can accomplish if we do.
精彩评论