Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

Aspect-Oriented Programming: Couple of Short Examples

by chunlou (Curate)
on Aug 05, 2003 at 09:40 UTC ( [id://280934]=perlmeditation: print w/replies, xml ) Need Help??

Suppose you have a bunch of scripts or modules like the following

        +--------------+     +-----------------+
        |   Script A   |     |     Script B    |
        +--------------+     +-----------------+
        | insert_alias |     | insert_Nickname |
        +--------------+     +-----------------+

where they insert "alias" into the database for the users from various applications. Each insert subroutine does basically the same thing, though the actual subroutines' names varies. After all the applications have been up and running for a while, now you decide that you don't want any user to use "Saint" as an alias anywhere in the system due to new business rules. Are you going to modify each insert subroutine script by script?

Well, perhaps. But, hopefully, not too much... if you use Aspect-Oriented Programming (AOP), like the following example (just another way to do things):

    +----------------------------------------------+
    |           Aspect Pre-Condition               |
    +----------------------------------------------+
    | advice(calls(qr/main::insert_.*/), sub{...}) |
    +----------------------------------------------+
                           ^
                           |
               +----------------------+
               |                      |
        +--------------+     +-----------------+
        |   Script A   |     |     Script B    |
        +--------------+     +-----------------+
        | insert_alias |     | insert_Nickname |
        +--------------+     +-----------------+

Let's try to impose the no-saint pre-condition check on this "alias" script:

use strict ; use warnings; sub insert_alias {print "insert $_[0]\n"; return 1} insert_alias('Mike'); insert_alias('SAINT');

and this "Nickname" script:

use strict ; use warnings; sub insert_Nickname {print "insert $_[0]\n"; return 1} insert_Nickname('Jack'); insert_Nickname('Jill'); insert_Nickname('SAINT'); insert_Nickname('Saint');

With AOP, we only have to add to two lines to the "alias" script.

# try_AspectPrecon1.pl use strict ; use warnings; use AspectPrecon qw/ pre_insert /; pre_insert()->enable; sub insert_alias {print "insert $_[0]\n"; return 1} insert_alias('Mike'); insert_alias('SAINT'); __END__ Sorry, SAINT has been reserved. at try_AspectPrecon1.pl line 10 main::insert_alias(Mike) is being called. insert Mike main::insert_alias(SAINT) is being called.

And this is the pre-condition aspect module that will impose the no-saint restriction.

package AspectPrecon; use strict ; use warnings; use Carp; use Exporter; use Aspect qw(advice calls); our @ISA = qw(Exporter); our @EXPORT_OK = qw(pre_insert); my $subc = sub { print $::thisjp->signature(@_) . " is being called.\n"; croak "Sorry, $_[0] has been reserved." if $_[0] =~ /saint/i; }; my $spec = qr/main::insert_.*/; my $pre_insert = advice(calls($spec), $subc); sub pre_insert {$pre_insert}; 1;

In the module, advice() will check what subroutines to look for, as determined by calls($spec), and what action to take, as defined by $subc, once a subroutine is found. calls() signifies the fact that action by $subc will be taken before a matching subroutine does anything at all--hence precondition.

Which subroutine advice() will take action on is determine by $spec, which is just a regex matching package::subroutine. The subroutines to be matched are all of them called directly and indirectly by the your module or script. So specifying packages in the regex is very important. In a script, you will use something like qr/main::.*/ at least.

Notice the definition of the anonymous subroutine $subc where $::thisjp->signature(@_) returns the matching subroutines along with the values passed to it, something like insert_alias('Mike'), and $_[0] or @_ are the one coming from the matching subroutine, so for insert_alias('Mike') matched by calls($spec) the $_[0] will be 'Mike'.

Now let's look at the "Nickname" script.

# try_AspectPrecon2.pl use strict ; use warnings; use AspectPrecon qw/ pre_insert /; sub insert_Nickname {print "insert $_[0]\n"; return 1} insert_Nickname('Jack'); pre_insert()->enable; insert_Nickname('Jill'); pre_insert()->disable; insert_Nickname('SAINT'); pre_insert()->enable; insert_Nickname('Saint'); __END__ Sorry, Saint has been reserved. at try_AspectPrecon2.pl line 17 insert Jack main::insert_Nickname(Jill) is being called. insert Jill insert SAINT main::insert_Nickname(Saint) is being called.

Notice you can turn the aspect on and off by pre_insert()->enable and pre_insert()->disable respectively. One handy use for this is you can turn an aspect on or off automatically depending on, say, whether you're on production or development environment by looking up the environment variable.

We've done the pre-condition. Let's try a post-condition. Let's say now you don't want to interfere with the users. But instead you merely want to be notified if a user uses 'Saint' as an alias. The code for the post-condition aspect module more or less has the same structure.

package AspectPostcon; use strict ; use warnings; use Exporter; use Aspect qw(advice returns); our @ISA = qw(Exporter); our @EXPORT_OK = qw(post_insert); my $subr = sub { print $::thisjp->sub . " has done its work.\n"; print "Tell God a $_[0] has been created.\n" if $_[0] =~ /saint/i; return 1; }; my $spec = qr/main::insert_.*/; my $post_insert = advice(returns($spec), $subr); sub post_insert {$post_insert}; 1;
Instead of calls(), now we use returns() in order to intercept a matching subroutine after it has done its work and returns its result. And we define the anonymous subroutine used by advice() accordingly.

The changes in the script are minimal, two lines.

Here

# try_AspectPostcon1.pl use strict ; use warnings; use AspectPostcon qw/ post_insert /; post_insert()->enable; sub insert_alias {print "insert $_[0]\n"; return 1} insert_alias('Mike'); insert_alias('SAINT'); __END__ insert Mike main::insert_alias has done its work. insert SAINT main::insert_alias has done its work. Tell God a SAINT has been created.

and there.

# try_AspectPostcon2.pl use strict ; use warnings; use AspectPostcon qw/ post_insert /; post_insert()->enable; sub insert_Nickname {print "insert $_[0]\n"; return 1} insert_Nickname('Jack'); insert_Nickname('SAINT'); insert_Nickname('Jill'); insert_Nickname('Saint'); __END__ insert Jack main::insert_Nickname has done its work. insert SAINT main::insert_Nickname has done its work. Tell God a SAINT has been created. insert Jill main::insert_Nickname has done its work. insert Saint main::insert_Nickname has done its work. Tell God a Saint has been created.

If OOP is a vertical view of a system according to its object hierarchy. AOP is a horizontal view across common concerns. AOSD is a good place to learn more about AOP.

Replies are listed 'Best First'.
Re: Aspect-Oriented Programming: Couple of Short Examples
by castaway (Parson) on Aug 05, 2003 at 10:41 UTC
    I think I vaguely understand what this is about. Could you add something about *what* AOP actually is, the concept? Or at least a link to something explaining it?

    Also the example seems poorly chosen, if that was done to a set of scripts several times, instead of maintaining them, all you'd end up with would be a mess. (IMHO!) - And it looks like more or as much work as actually putting those similar routines into a module. Where is this actually useful?

    C.

      The basic idea behind AOP is separation of concerns. By "concern" I mean, roughly, a "bit of functionality". You want all you logging in one place. You want all your DB functionality in one place. You want all of your persistence layer in one place. Etc.

      The basic idea of AOP is that decomposition into classes in a classic OO design/implementation can miss some separation of concerns.

      The classic AOP example is logging. If you have a system where you are required to log each method entry and exit you need to do something like:

      sub foo { my $self = shift; $self->logger->log('entering foo'); .... $self->logger->log('exiting foo'); };

      to every method. This has two problems:

      • It's a lot of monkey coding. Easy to make mistakes. Easy to miss a method out.
      • The "concern" of "every method should log entry and exit" is spread out over the entire codebase. It isn't in one location. If you decide that you only want to log method entry you have to go change every method call.

      In AOP you can have a single aspect that says "add this logging code to every method". No monkey coding. No possibility of missing anything. The concern is documented in one place. If you need to change what is logged, or what methods/classes logging should happen in, you only have to change one thing.

      Introduction to Aspect Oriented Programming with AspectJ has an overview of some of the Jargon, although (obviously) Java oriented. There is also an mp3 of the lightning talk that Marcel Grunauer gave on Aspect at YAPC::North::America 2001.

      AOP can be useful in static languages since it can provide you with a lot of additional flexibility (e.g. you can use AspectJ to add mixins to Java). They are of less relevance to more dynamic languages - especially those with a decent meta-class system. Although, on the Java side, aspects are a compile-time concept so they can be very efficient.

      AOP can also make testing and debugging harder (IMO).

      I have occasionally found AOP useful in debugging. For example Fun with Hook::LexWrap and code instrumentation could be viewed as AOP.

        This looks like a kind of parse tree filter, as opposed to source filter. Whenever you have a function in the tree you add something to it.
        Perhaps stereotyping the behavior of a set of methods/functions could be handled just as well by Attribute::Handlers ?

        Carter's compass: I know I'm on the right track when by deleting something, I'm adding functionality

        Could you elaborate a bit what you meant by "static language" versus "dynamic language"? I don't think I understand. Thanks.
      AspectJ probably has one of the best documentation. It takes time and practice for the concept of AOP to sink in. Xerox develops AOP. Theirs and many other AOP documentation read like a philosophical essay. Hard to find a good one.
Re: Aspect-Oriented Programming: Couple of Short Examples
by rir (Vicar) on Aug 05, 2003 at 17:25 UTC
    Fragile stuff. Ugliness starts when someone adds an insert_XXX that inadvertently matches the pattern of an aspect.

      Depends on the context.

      Just because a technique can cause fragility does not mean that the entire technique is worthless. For example, I still use inheritance when appropriate - despite the fragile base class problem - because the benefits outweigh the problems.

      AOP can be a powerful technique. It has an upside and a downside. Sometimes the benefits outweigh the costs. Sometimes the costs outweigh the benefits.

      For example, implementing logging or persistence via aspects can make your application a lot less fragile in the face of change. If you need to change your logging system or persistence layer you just need to alter a single aspect - rather than manually update thousands of methods.

      Sometimes, especially in languages like Java, AOP is the best technique to pick.

        Depends on the context.

        I was responding to the mechanism as presented in this thread. I apologize for being unclear.

        Just because a technique can cause fragility does not mean that the entire technique is worthless.

        I don't think anyone suggested that the technique had no worth. Invasive code altering techniques like this have incredible power. The weakness is the shabby instrumentation used to control this power; correctly constructing the regexes requires visiting every function that falls within the applicable scope. Thereafter safely adding any function requires finding and reviewing every applicable regex.

        For example, I still use inheritance when appropriate - despite the fragile base class problem

        This is a very similar issue. It's nearly equivalent when the maintainers of the base class might be unaware that their code is being inherited. My first serious criticism of C++ was for its lack of a DO_NOT_INHERIT_FROM_ME mechanism.

        aspects can make your application a lot less fragile in the face of change.

        You're entitled to your opinion. I would like to see you support it.

        If you need to change your logging system or persistence layer you just need to alter a single aspect - rather than manually update thousands of methods.

        This is not support. This only points again to the power of AOP to quickly make sweeping changes, not to the resilience of the resultant code.

        I say there are no protections built into the system and that the locality of lexical reference is wrong. I can't imagine I'd plan to use AOP on a codebase that has not been written. Rather than use AOP I'd prefer to go one step further and use the regex and another tool to explicitly insert the desired code into the desired functions.

        If I am missing some bit of AOP that addresses these issues I'd be glad to be educated.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://280934]
Front-paged by broquaint
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others meditating upon the Monastery: (3)
As of 2024-03-29 02:18 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found