An aspect is a software entity implementing a specific non-functional part of the application.
Using AOP has 2 Benefits
- The logic for each concern is now in one place, as opposed to being scattered all over the code base.
- Classes are cleaner since they only contain code for their primary concern (or core functionality) and secondary concerns have been moved to aspects.
OOP and AOP are not mutually exclusive. AOP can be good addition to OOP. AOP is especially handy for adding standard code like logging, performance tracking, etc. to methods without clogging up the method code with this standard code.
Assume you have a graphical class with many “set…()” methods. After each set method, the data of the graphics changed, thus the graphics changed and thus the graphics need to be updated on screen. Assume to repaint the graphics you must call “Display.update()”. The classical approach is to solve this by adding more code. At the end of each set method you write
void set...(...) { : : Display.update(); }
If you have 3 set-methods, that is not a problem. If you have 200 (hypothetical), it’s getting real painful to add this everywhere. Also whenever you add a new set-method, you must be sure to not forget adding this to the end, otherwise you just created a bug.
AOP solves this without adding tons of code, instead you add an aspect:
after() : set() { Display.update(); }
And that’s it! Instead of writing the update code yourself, you just tell the system that after a set() pointcut has been reached, it must run this code and it will run this code. No need to update 200 methods, no need to make sure you don’t forget to add this code on a new set-method. Additionally you just need a pointcut:
pointcut set() : execution(* set*(*) ) && this(MyGraphicsClass) && within(com.company.*);
What does that mean? That means if a method is named “set*” (* means any name might follow after set), regardless of what the method returns (first asterisk) or what parameters it takes (third asterisk) and it is a method of MyGraphicsClass and this class is part of the package “com.company.*”, then this is a set() pointcut. And our first code says “after running any method that is a set pointcut, run the following code”.
See how AOP elegantly solves the problem here? Actually everything described here can be done at compile time. A AOP preprocessor can just modify your source (e.g. adding Display.update() to the end of every set-pointcut method) before even compiling the class itself.
However, this example also shows one of the big downsides of AOP. AOP is actually doing something that many programmers consider an “Anti-Pattern”. The exact pattern is called “Action at a distance”.
Action at a distance is an anti-pattern (a recognized common error) in which behavior in one part of a program varies wildly based on difficult or impossible to identify operations in another part of the program.
As a newbie to a project, I might just read the code of any set-method and consider it broken, as it seems to not update the display. I don’t see by just looking at the code of a set-method, that after it is executed, some other code will “magically” be executed to update the display. I consider this a serious downside! By making changes to a method, strange bugs might be introduced. Further understanding the code flow of code where certain things seem to work correctly, but are not obvious (as I said, they just magically work… somehow), is really hard.
OOP and AOP are not mutually exclusive. AOP can be good addition to OOP. AOP is especially handy for adding standard code like logging, performance tracking, etc. to methods without clogging up the method code with this standard code.
AOP addresses the problem of cross-cutting concerns, which would be any kind of code that is repeated in different methods and can’t normally be completely refactored into its own module, like with logging or verification
function mainProgram() { var x = foo(); doSomethingWith(x); return x; } aspect logging { before (mainProgram is called): { log.Write("entering mainProgram"); } after (mainProgram is called): { log.Write( "exiting mainProgram with return value of " + mainProgram.returnValue); } } aspect verification { before (doSomethingWith is called): { if (doSomethingWith.arguments[0] == null) { throw NullArgumentException(); } if (!doSomethingWith.caller.isAuthenticated) { throw Securityexception(); } } }
And then an aspect-weaver is used to compile the code into this:
function mainProgram() { log.Write("entering mainProgram"); var x = foo(); if (x == null) throw NullArgumentException(); if (!mainProgramIsAuthenticated()) throw Securityexception(); doSomethingWith(x); log.Write("exiting mainProgram with return value of "+ x); return x; }
Cross Cutting Concerns
- Database Access
- Data Entities
- Email/Notification
- Error Handling
- Logging