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.
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.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;
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.
|
---|