Pure Aspect-Oriented Program: an example

Hung Jung Lu hungjunglu at yahoo.com
Thu Nov 6 06:19:38 CET 2003


Hi,

I have been looking into AOP (Aspect-Oriented Programming) for
sometime, now. I frankly don't like the syntax of any of the
approaches I have seen so far. I am kind playing around with some
ideas, and maybe write up an article later.

AOP is not just buzzword. It's not just callback, it's not just Ruby's
MixIn, it's not just Python's metaclass, it's not just C++'s template.
AOP can be implemented/considered as a subset of metaprogramming... an
important subset that stands on its own. AOP deserves its name,
because one really can think and program in "aspects" instead of
"objects". That being said, I have never seen an example of purely
aspect-based program. So, I thought I'd write up one.

Here it is, and let's get some feedback.

thanks,

Hung Jung

--------------------

Suppose you have to implement a checking account that allows
withdrawal of money. Your first simple implementation may look like:

class Account {
    real balance
    method withdraw(amount) {
        this.balance = this.balance – amount
    }
}

A program that withdraws money may look like:

account = new Account()
	...
amount = 100
account.withdraw(amount)

In order to simplify the discussion, in the following I will omit all
initialization code details, and assume that variable values have been
properly assigned elsewhere. Also notice that everything is in
pseudocode, I am not using any particular language as base.

After you have implemented the program, your bank manager comes and
tells you there are constraints to the withdrawal of money. For
instance, an account maybe disabled. In OOP, you would modify your
Account class to:

class Account {
    real balance
    bool enabled
    method withdraw(amount) {
        if not this.enabled 
            raise AccountDisabledException
        this.balance = this.balance – amount
    }
}

Now, stop thinking in OOP. Let us try to think in AOP.

//---------------------------------------------------
class Account {
    real balance
    method withdraw(amount):
        this.balance = this.balance – amount
}

//---------------------------------------------------
aspect AccountStatus {
    bool enabled
    codeblock check_status {
        if not this.enabled:
            raise AccountDisabledException
    }
    method withdraw(...) {
        this.check_status
        this.withdraw.code
    }
}

//---------------------------------------------------
endow Account with AccountStatus


The general idea is to have the concerns implemented outside the
object. We have added a new feature to the account (namely, the
account status,) without having to tweak the original code of the
account. Notice that we have three parts to the above code: a class
definition, an aspect definition, and a final part to endow the aspect
to the class. The last part is also known as the "aspect weaver".

Let us proceed to the next step. Say, there is a maximum daily
withdrawal limit.

//---------------------------------------------------
class Account {
    real balance
    method withdraw(amount) {
        this.balance = this.balance – amount
    }
}

//---------------------------------------------------
aspect AccountStatus {
    bool enabled
    codeblock check_status {
        if not this.enabled:
            raise AccountDisabledException
    }
    method withdraw(...) {
        this.check_status
        this.withdraw.code
    }
}

//---------------------------------------------------
aspect WithdrawalLimit {
    real daily_limit
    real withdrawn_today
    codeblock check_and_update_withdraw {
        new_withdrawn_today = this.withdrawn_today + amount
        if new_withdrawn_today > this.daily_limit:
            raise WithdrawalLimitExceededException
        &inner_code
        this.withdrawn_today = new_withdrawn_today
    }
    method withdraw(...) {
        this.check_and_update_withdraw {
            ...
            &inner_code = this.withdraw.code
            ...
        }
    }
}

//---------------------------------------------------
endow Account with AccountStatus
endow Account with WithdrawalLimit

Notice the usage of a hook (also known as pointcut): &inner_code. In
general, a code block or a method can contain one or many hooks. Hooks
allow future enhancement of code. There are two implicit hooks: the
before and the after hooks. But for readability of AOP code, in our
approach we treat named hooks very differently from implicit hooks.
Ruby users should notice that a code block here can contain multiple
hooks, and that the direction of hooking process is kind of opposite
to Ruby's MixIn.

The above way of programming of course takes longer to write. But, the
advantage is in the degree of decoupling (or "separation of concerns")
that is achieved. Say, one day, we want to eliminate the feature on
withdraw limit, it is as simple as commenting out one single line of
code:

endow Account with AccountStatus
//endow Account with WithdrawalLimit

The same is true if one day we want to eliminate the account status
feature:

//endow Account with AccountStatus
endow Account with WithdrawalLimit

Also, once the concerns are separated, it is easier to modify the
aspects individually.

A careful reader would point out that we may want to check the account
status before checking withdrawal limit. Notice also that we have
applied two aspects in sequence. But we can pre-compose two aspects
into one, and apply the composed aspect just once.

//---------------------------------------------------
aspect AccountAspects inherits AccountStatus, WithdrawalLimit {
    method withdraw(...) {
        this.check_status
        this.check_and_update_withdraw {
            ...
            &inner_code = this.withdraw.code
            ...
        }
    }
}
//---------------------------------------------------
endow Account with AccountAspects

At this point you may say: "Hey! We started with a non-trivial class

but we could have started with an empty class, and endow the
balance-calculating feature as an aspect, too." Bingo! Now you are
thinking in AOP.

//---------------------------------------------------
class Account {
}
//---------------------------------------------------
aspect BalanceKeeping {
    real balance
    method withdraw(amount) {
        this.balance = this.balance – amount
    }
}

//---------------------------------------------------
aspect AccountStatus {
    bool enabled
    codeblock check_status {
        if not this.enabled:
            raise AccountDisabledException
    }
    method withdraw(...) { // this meta-method is overriden later
        this.check_status
        this.withdraw.code
    }
}

//---------------------------------------------------
aspect WithdrawalLimit {
    real daily_limit
    real withdrawn_today
    codeblock check_and_update_withdraw {
        new_withdrawn_today = this.withdrawn_today + amount
        if new_withdrawn_today > this.daily_limit:
            raise WithdrawalLimitExceededException
        &inner_code
        this.withdrawn_today = new_withdrawn_today
    }
    method withdraw(...) { // this meta-method is overriden later
        check_and_update_withdraw {
            ...
            &inner_code = this.withdraw.code
            ...
        }
    }
}

//---------------------------------------------------
aspect AccountAspects: inherits BalanceKeeping, 
                                AccountStatus, 
                                WithdrawalLimit {
    method withdraw(...) {
        check_status
        check_and_update_withdraw { // this meta-method overrides
            ...
            &inner_code = this.withdraw.code
            ...
        }
    }
}
//---------------------------------------------------
endow Account with AccountAspects
print Account.codeString()

Notice that we have started with a bare class with no attributes. All
the features of the class Account have been implemented by aspects,
instead of interfaces or base classes. You may complain: the code is
now too hard to read and follow. But that actually is a problem of IDE
(Integrated Development Environment). For more advanced IDEs, editing
aspects actually should not be bad at all. It's almost like editing
the header and footer information in a Microsoft Word document. At any
rate, the last statement above would print out the code for the
"aspected class", which may look something like:

class Account {
    real balance
    bool enabled
    real daily_limit
    real withdrawn_today
    method withdraw(amount) {
        if not this.enabled:
            raise AccountDisabledException
        new_withdrawn_today = this.withdrawn_today + amount
        if new_withdrawn_today > this.daily_limit:
            raise WithdrawalLimitExceededException
        this.balance = this.balance – amount
        this.withdrawn_today = new_withdrawn_today
    }
}

A good IDE/compiler/debugger can provide additional information on
where each code line or code block comes from, hence making debugging
and editing a snap. But we don't need to get into that discussion,
now.

What have we learned? We have learned that:

1.	We can write a purely-AOP program.
2.	Aspect inheritance can be used to compose derived aspects.
3.	A new type of object: "codeblock", becomes the fundamental building
block of programs. So, in general we have to consider the (data,
codeblock, method) trio when building classes or programs. A method
consists of its "header/prototype" and a reference to its codeblock.
Also, in a real program, most codeblocks would be anonymous.
4.	Despite of its innocent look, the code specifying an aspect
contains disguised meta-programming instructions.

Codeblock-based AOP actually is more suitable in programming languages
powered with meta-programming features. Also, (data, codeblock,
method) are all supposed to be virtual and overridable. Data
overriding does not do anything, if the data is already present.
Codeblock overriding does not present problem. The main problem is
with method overriding. If the new method refers to the existing
method, there is a problem as how to deal with the names and who will
hold reference to the old method once the new method is in place. For
the AOP practitioners, this means that the "before" and "after"
advices can be implemented more easily, where as the "around" advice
is trickier. A possible solution is to "decorate" the name of the old
implementation. Here I only present a possible syntax of the "around"
advice.

class C {
    method f() {
        return 3
    }
}

aspect A {
    method f_before_A = f
    method f(...) {
        print 'begin around-advice'
        result = this.f_before_A()
        print 'end around-advice'
        return result
    }
}

endow C with A
c = new C()
print c.f()

//------ output
begin around-advice
end around-advice
3

One thing I have not mentioned is pattern matching for method names.
An aspect may affect many or all the methods in a class. In that case,
the name of the methods should be listed, or wildcards (possibly
regular expressions) must used to pattern-search for them. Possible
syntax variations are:

aspect A {
    method <f>(...) for f in f1, f2, f3 {
    }
}

aspect B {
    method <f>(...) for f matching 'f*' {
    }
}

Another thing that I have not mentioned is that aspects can be endowed
to different classes. For instance, if later we have checking accounts
and savings accounts, we can apply the same aspect to both classes.
That's what other people refer to as "cross-cutting".

... That's all for now.




More information about the Python-list mailing list