You are not logged in Log in Join
You are here: Home » Members » Phillip J. Eby's Zope Center » My Wikis » TransWarp Wiki » AOPTutorial » wikipage_view

Log in
Name

Password

 
 
HomePage »

AOPTutorial

Doing AOP With TransWarp (Under Construction)

The goal of this tutorial is to provide you with all the information and examples you need to use TransWarp's AOP tools. There are many links from this tutorial which go to more technical Wiki pages about the implementation of those tools. You don't kneed to know those implementation details, however, if your goal is simply to do aspect-oriented programming. The links are simply there if you are curious, or if you want to create your own additions to the TransWarp family of tools (in which case you will need to know more about the internal workings of the framework).

Also, as you go through the tutorial, you may find it helpful to try the examples yourself in the Python interpreter, and inspect the various aspects, classes, and objects created to learn more about how the AOP system actually works.

Introduction: Components, Class Families, and Reuse

The Goal: Black Box Reuse

For decades now, the holy grail of reusability has been the notion of the "component" - a black-box reusable piece of code. Unfortunately, almost by definition, if you're going to reuse a piece of code as a black box, you have to be able to use it without changing it. That means that the code has to have been capable from the start of doing what you want, even if it's by way of parameterizing. What good are thousands of reusable components, if none of them do precisely what you want, and you can't mix and match the pieces from each that do what you want?

Many programming approaches offer partial solutions. Inheritance lets you extend an existing class. Certain design patterns help to define good hookpoints for overriding things in subclasses, or factor out overrideable behavior into collaborator objects which can be supplied by the (re)user of the code.

But those are just partial solutions, applicable only on a class-by-class basis. A real solution for components must address not just individual classes, but groups of classes that collaborate. To be worthy of the name "component", they need to be composable, as well. That is, it should be possible to build components not only from classes, but from other components as well.

In summary, a good component architecture must meet the following "3 C's" requirements:

  • Customization - Components must be customizable without changing them (otherwise, you couldn't reuse the same component for different things!)
  • Collaborators - It should be possible to substitute or customize a component's collaborator components or classes.
  • Composition - It should be possible to use a component as a collaborator in developing a larger component

How can we achieve such a component architecture? To meet the "Customization" requirement, it would seem that we need a way to create new versions of a component, perhaps stamping them out of a template, or having some kind of factory object which can be told what we want.

Further, if we could define partial templates, which were not sufficient to create components by themselves, but which could be combined with other partial templates, we would have the ultimate in mix-and-match customizability, composition, and collaboration.

At this point, our actual unit of reuse becomes the factory or template, rather than the component itself. The template does not change, and can be distributed and reused in as many applications as desired, but the individual components created by each (re)user can be quite different.

This sounds a lot like a class and its instances, but at a higher level. It's even different from the notion of a metaclass, because metaclasses are templates for stamping out single classes, and what we're talking about needs to stamp out entire families of classes, with arbitrary numbers of classes involved in a single instantiation.

So how could we create such a template?

The Aspect - A Template for Components

In TransWarp, an "Aspect" is a template for components. It can be complete or partial, and it can be combined with other aspects to create new aspects.

An aspect can be thought of as being like a transparency used on an overhead projector. Projecting light through the transparency creates an image. Adding additional transparencies changes the image, and the order in which they are placed can make a difference in the image as well. Many different small transparencies (or multiple copies of the same one) can be placed atop a larger transparency, which may itself be being used as part of a still larger transparency. Further, we can take the projected image and turn it into a new transparency, or capture the image as a printed photograph (a component).

In TransWarp parlance, a "component" is a class and its collaborators (which may themselves be components). To be useful, these classes must of course be instantiated. To continue our transparency analogy, this would be like using the finished photograph (component) as a guide to building the thing depicted in the photo (instance). To summarize our analogy:

  • Transparency = Aspect = Component Template
  • Photograph = Class Family = Component
  • Thing In Photo = Class Instance = Component Instance

"Weaving" an aspect (instantiating a component) creates a brand new class family: every class in it is specifically created for that instantiation. This means that the same template can be used more than once in the same application, to produce seperate (but similar) class families if they are needed for different purposes.

In Python applications, most classes are created when the modules that define them are imported. This does not change in a TransWarp application, but the process of creating those classes involves one extra step. Let's begin with a very simple example:

    from TW.Aspects import Aspect

    class MyAspect(Aspect):
        pass        

    MyComponent = MyAspect(name="MyComponent")

In this example, we create an aspect called MyAspect, and then call it to instantiate a component class called MyComponent. (We pass in the name so that our created class will have its __name__ attribute set correctly.)

Now, if we want to create an instance of our class, we can do:

    aComponent = MyComponent()

Aspects Are Not Classes

At this point, all the aspect stuff looks like meaningless overhead, just as writing a class is meaningless overhead if you have only one instance and will never reuse and customize it for different purposes. Let's look at a slightly more meaningful (although still trivial) example:

    from TW.Aspects import Aspect

    class MyAspect(Aspect):

        class Thing:
            def printMe(self):
                print "Hi, I'm a", self.__class__.__name__

        class Rock(Thing):
            pass

        class Dog(Thing):
            pass

    MyComponent = MyAspect(name="MyComponent")

This example creates MyComponent again, but this time as a class with three attributes: Thing, Rock, and Dog. If we were to type the code above into the Python interpreter, and inspect the objects, we would see something like this:

    >>> MyAspect
    <Aspect MyAspect at 9812672>

    >>> MyComponent
    <class TW.Aspects.MyComponent at 949150>

    >>> 

Notice that MyAspect is not a class, even though we created it using a class statement. It is an Aspect object. Let's take a closer look:

    >>> dir(MyComponent)
    ['Dog', 'Rock', 'Thing', '__doc__', '__module__']

    >>> MyComponent.Rock
    <class __main__.MyComponent.Rock at 949ef0>

    >>> dir(MyAspect)
    ['__bases__', '__doc__', '__name__', 'componentBases', 'dictionaries']

    >>> MyAspect.keys()
    ['Rock', 'Dog', 'Thing']

    >>> MyAspect['Rock']
    <class __main__.Rock at 95b930>

    >>> 

Aspects Make Classes - And Class Families

Notice that MyComponent behaves just like a standard Python class - because it is one. MyAspect, on the other hand, acts more like a dictionary than a class. Also, notice that MyAspect contains the class __main__.Rock (the original Rock class), while MyComponent contains a new class, __main__.MyComponent.Rock. Each class in our original template has been stamped out to form a new class in the output component. Let's try making another component and see what happens:

    >>> New = MyAspect(name="New")

    >>> New
    <class TW.Aspects.New at 9f5960>

    >>> New.Rock
    <class __main__.New.Rock at 9f56b0>

    >>> 

Another component, another class. Now let's take a look at the ancestry of 'Rock':

    >>> MyAspect['Rock'].__bases__
    (<class __main__.Thing at 95b840>,)

    >>> MyComponent.Rock.__bases__
    (<class __main__.MyComponent.Thing at 9498f0>,)

    >>> New.Rock.__bases__
    (<class __main__.New.Thing at 9f4360>,)

    >>> 

Notice that each of our Rock classes are based on a related Thing class; they do not all inherit from the original Thing class. This is important: it means that when we combine and extend aspects, we can customize base classes as well as their subclasses.

In other words, Aspects create entire class families, which are contained in an overall class known as the "component". Let's recap what we've seen so far.

  • Aspects are created using a class statement which includes (directly or indirectly) Aspect as the first base class.
  • Aspects can contain other classes, through nested class statements, nested import statements, or nested assignment statements.
  • Aspects are not classes, but instead are a special kind of object which can be used to replicate the class structure contained in them. The new class structure produced is called a "component".
  • To create a component from an aspect, call the aspect, passing in name="desired name for the component".
  • The classes inside the component are given dotted names based on the name of the component, and they inherit from each other rather than from the template classes they were replicated from.

Customizing Components

Overlaying Aspects

Clearly, it's not of much use to stamp out virtually identical class families over and over again. We want to be able to customize them in some way. How do we do this?

If we return to our analogy of transparencies, we quickly see that if we do not actually change a transparency, the only way to produce a different image is to combine transparencies, by laying one atop another. Let's look at how to do that in Python:

    class Weighing(Aspect):

        class Thing:
            weight = 0

            def getWeight(self):
                return "%s pounds" % self.weight

        class Dog:
            weight = 50

        class Rock:
            weight = 15

    WeightyThings = (MyAspect+Weighing)(name="WeightyThings")

Now we have a new aspect, Weighing, designed to be overlaid upon our first aspect, MyAspect. We overlay it by adding it to the existing aspect, which creates a new aspect. We then call that new aspect to create a new component, WeightyThings. Let's take a closer look at it:

    >>> WeightyThings
    <class TW.Aspects.WeightyThings at 9c1d00>

    >>> WeightyThings.Rock
    <class __main__.WeightyThings.Rock at 9c1900>

    >>> WeightyThings.Rock.weight
    15

    >>> WeightyThings.Rock.printMe
    <unbound method WeightyThings.Thing.printMe>

    >>> WeightyThings.Dog.getWeight
    <unbound method WeightyThings.Thing.getWeight>

    >>> WeightyThings.Thing.printMe
    <unbound method WeightyThings.Thing.printMe>

    >>> WeightyThings.Thing.getWeight
    <unbound method WeightyThings.Thing.getWeight>

    >>> 

Our new component's contained classes have their own weight attributes and printMe() and getWeight() methods inherited from WeightyThings.Thing. WeightyThing.Thing, meanwhile, contains code and data from both MyAspect and Weighing. We've just customized our original aspect to create our own variant of the original component.

Notice that we did not need to change the source of the original aspect in any way. Also notice that our second aspect is only a partial definition and does not reference the first aspect in any way. This means that someone (re)using these aspects can choose how they will be combined with other aspects they may wish to add. That is, they can choose which transparencies they will layer together, and in what order.

Aspect Arithmetic and Inheritance

Adding aspects together produces a new aspect. For numbers, it doesn't matter what order you add them in, but for aspects, the order can make a difference, just as with transparencies. For our examples thus far, the order of combination is unimportant, because neither aspect overrode definitions from of the other. This is similar two two transparencies, one of which is blank on its left side, the other blank on the right. If you place these atop each other, it doesn't matter what order you put them in because the non-blank sides show through the blank sides.

But what if one transparency is blank on the left, and the other is blank on the bottom? Then the order will make a difference to the upper right-hand corner of the image, as one transparency or the other will dominate there. What does this look like in terms of classes? Let's see:

    class Chatty(Weighing):

        class Thing:

            def printMe(self):
                print "Hi, I'm a",self.__class__.__name__,
                print "and I weigh", self.getWeight()

    ChattyThings = (MyAspect + Chatty)(name="ChattyThings")

This new aspect, Chatty, redefines the printMe() method to include the weight of the Thing it is describing. Because this requires a getWeight() method, we derive Chatty from Weighing instead of creating it as a new aspect from scratch. In general, however, you should avoid deriving an aspect from another in this way, as it reduces reusability. It's sort of like fusing two transparencies together to make a new one: it's easier to handle just one, but if you keep them seperate, there's always the option of placing other transparencies between them. As with class inheritance, an aspect which inherits from another aspect is effectively fused to the first one, and you cannot insert new layers between the two.

Because it redefines printMe() method, the Chatty aspect is also order-sensitive when combined with MyAspect. If we were to do this:

    ChattyThings = (Chatty + MyAspect)(name="ChattyThings")

We would get a very different result. Adding MyAspect+Chatty causes the definitions in Chatty to override any in MyAspect, while adding Chatty+MyAspect causes definitions in MyAspect to override those in Chatty.

Although aspect addition is not commutative (i.e., it's potentially order-sensitive), it is associative. That is, (Aspect1+Aspect2)+Aspect3 produces an equivalent result to Aspect1+(Aspect2+Aspect3). This is perfectly in keeping with our transparency analogy: if the overall order is the same, it doesn't matter whether you stack transparencies on the projector one at a time or in previously stacked batches.

Let's look at another way to make 'ChattyThings?':

    class Chatty2(Aspect):

        class Thing:

            def printMe(self):
                print "Hi, I'm a",self.__class__.__name__,
                print "and I weigh", self.getWeight()

    ChattyThings2 = (MyAspect + Weighing + Chatty2)(name="ChattyThings2")

This time, we didn't derive Chatty2 from Weighing, so we must ensure it's there when we create our final component. Notice, by the way, that because the Weighing aspect doesn't override anything in the other two aspects, or vice versa, it doesn't matter where Weighing is placed in the list of aspects being added. It could be first, second, or third. The only ordering constraint here is that Chatty2 must come after MyAspect so that it can override the printMe() method.

Inheritance Inside Aspects

Sharp-eyed readers will have noticed some time back that the Rock and Dog class fragments in our Weighing aspect do not include Thing in their base classes, and that Chatty and Chatty2 do not even define the Rock and Dog classes! Yet, the WeightyThings and ChattyThings components contain Rock and Dog classes which inherit from the respective Thing classes. How can this be so?

We already know that TransWarp combines the definitions of the nested classes. Clearly, this combination includes base classes, or else Dog and Rock wouldn't be inheriting from Thing. Specifically, when classes are combined to form a new class, their base class lists are added together, and duplicates are removed. The overall order of base classes, however, is preserved, based on the order in which the aspects are combined.

However, this doesn't explain why everything ends up inheriting from a new class. What's happening is that base classes which are present in their parent class' dictionary are referenced by name, not by value. In other words, when generating a component, the aspect sees that Rock has a base class Thing which is defined in the same outer class, and substitutes the string "Thing" for the actual Thing class in the list of bases. Then, once the Thing class has been generated, the Rock class is generated with the new Thing class in the correct place in its __bases__ list.

When a class inherits from another class in the same aspect like this, it is said to have "vertical inheritance" (implying a reference upward in the source code), and the class it inherits from is said to be a "vertical base". In our examples so far, we have dealt only with vertical inheritance and bases.

The other kind of inheritance that a class in an aspect can have, is when it inherits from a class outside the aspect. This is called "horizontal inheritance", to imply a reference "sideways" to another source file. The class inherited from is referred to as a "horizontal base". TransWarp does not do anything special with horizontal bases; it simply leaves them as-is in the output class' base list. This allows you to include special base classes in your aspects that you don't want redefined. (For example, ZODB's Persistence.Persistent mixin.)

Let's recap.

  • Aspects can be combined by adding them together, or extended through inheritance.
  • Extending through inheritance is less flexible since other aspects cannot be layered between the extended aspect and its base. If seperate aspects were created, then added, the pieces would be individually usable and re-combinable.
  • When aspects are combined, so are their nested classes or aspects.
  • When classes or aspects are combined, so are their base class lists, and "vertical bases" are replaced with a generated (and combined) class of the same name.
  • Combining aspects is associative but not commutative. That is, grouping doesn't make a difference, but order often does.

Using Components

Now that you know how to make aspects and combine them to create custom components, what good is it?

XXX to be continued... XXX

Basic Techniques

  • It's not a class until you weave it
  • Done all at once so exact implementation data is known, allowing for possibility of optimization features/transforms
  • Simple way to weave - just call the aspect
    • Optional parameters - name= and module=, recommended for persistence
      • name vs. __name__ side note - nested classes have nested names, but still accessible as self.classname
    • Other parameters, for advanced usage
  • Woven aspect is a plain class or extensionclass, with nested classes all the way down. Can now be used to make instances like a regular class.
  • Style note: name aspects using gerunds (e.g. "Parsing") or descriptive words/phrases (e.g "Colorable", "Persistent") rather than nouns

Combining Aspects

  • Using Addition
  • Using Inheritance
  • Precedence/ordering of feature overrides
  • Inheritance Among Nested Classes
    • "Horizontal" vs. "Vertical" Inheritance
      • Nested classes may inherit from each other - the relationship will be preserved in the final class hierarchy by name. (need example)
      • Inheritance from classes not contained in the aspect will be preserved by value.
    • Base class list in output classes is left-to-right "first come, first served" from source aspects.

Intermediate Techniques

  • How Features Are Combined
  • Using FeatureDef's
  • Using Transforms
  • Handling Persistence

Advanced Techniques

  • Creating new FeatureDef classes
  • Creating new Transform classes
  • Programmatically Creating Aspects
  • Places to look if you want to create far-out meta-stuff