Section 8.2. JOIN POINT INTERCEPTION


8.2. JOIN POINT INTERCEPTION

In this section, we discuss the deficiencies of a JPI-based approach to aspect structuring. The discussion in this section is in no way a critique of the notions of JPIs and advices. On the contrary, these are pivotal concepts of aspect-oriented languages. This work emphasizes the need for higher-level module concepts on top of them.

To illustrate the problems with JPI, we examine the implementation of the observer pattern in AspectJ proposed by Hannemann and Kiczales [12], shown in Listings 8-1 and 8-2. Listing 8-1 shows a reusable implementation of the observer protocol in AspectJ, while Listing 8-2 binds it to particular classes.

Listing 8-1. Reusable observer protocol in AspectJ
 public abstract aspect ObserverProtocol {  protected interface Subject { }  protected interface Observer { }  private WeakHashMap perSubjectObservers;  protected List getObservers(Subject s) {    if (perSubjectObservers == null)        perSubjectObservers = new WeakHashMap();    List observers =      (List) perSubjectObservers.get(s);    if ( observers == null ) {      observers = new LinkedList();      perSubjectObservers.put(s, observers);    }    return observers;  }  public void addObserver(Subject s,Observer o){    getObservers(s).add(o);  }  public void removeObserver(Subject s,Observer o){    getObservers(s).remove(o);  }  abstract protected void    updateObserver(Subject s, Observer o);  abstract protected pointcut subjectChange(Subject s);  after(Subject s): subjectChange(s) {    Iterator iter = getObservers(s).iterator();    while ( iter.hasNext() )        updateObserver(s, ((Observer)iter.next()));  } } 

Listing 8-2. Binding of observer protocol in AspectJ
 public aspect ColorObserver extends ObserverProtocol   declare parents: Point implements Subject;   declare parents: Line implements Subject;   declare parents: Screen implements Observer;   protected pointcut subjectChange(Subject s):     (call(void Point.setColor(Color)) ||     call(void Line.setColor(Color)) ) && target(s);   protected void updateObserver(Subject s, Observer o) {      ((Screen)o).display("Color change.");   } } 

The basic idea in Listing 8-1 is that the aspect ObserverProtocol declares an abstract pointcut that represents change events in the Subject classes. The empty interfaces Subject and Observer are marker interfaces that are used in the binding to map the application classes to their roles. The observers for each subject are stored in a global WeakHashMap (the weak references are required in order to prevent a memory leak) that maps a subject to a list of observers. In case of a subject change, all observers are notified by means of the abstract method updateObserver(). This method is overridden in the binding aspect in order to fill in the appropriate update logic.

This organization has two primary advantages. First, the code in Listing 8-1 is indeed a reusable implementation of the observer protocol: Nothing in the implementation is specific to a particular binding of this functionality. This is because the authors recognize the need to separate aspect implementation and aspect binding. Second, the same role, e.g., Subject, can be mapped to multiple different classes, e.g., Point and Line, as in Listing 8-2. It would also be no problem to assign two roles, e.g., Subject and Observer, to the same class or to assign the same role twice to the same class in two different bindings. For example, a Point can be simultaneously a subject concerning coordinate changes as well as color changes. In terms of [24], the observer "component" in Listing 8-1 is independently extensible.

These features are probably the rationale for the author's decision against an alternative (simpler) implementation: declaring addObserver() and removeObserver() in the interface Subject and then (in the binding) injecting these methods into the corresponding classes by means of a so-called introduction, AspectJ's open class mechanism. Similarly, a LinkedList could be introduced into every Subject class, thereby rendering the perSubjectObservers map unnecessary. However, with this solution, a class could not have two different instantiations of the Subject role, because then the class would have multiple implementations of the same method (e.g., addObserver()), producing a compilation error. We would lose independent extensibility.

Now, let us take a critical look at this solution and identify the problems.

8.2.1. Lack of Support for Multi-Abstraction Aspects

Note that all methods in Listings 8-1 and 8-2 are top-level methods of the enclosing aspect class. For example, addObserver(), which is conceptually a method of the subject role, is a top-level method whose first parameter is the respective Subject object. This design leads to a poor separation of concerns inside the aspect: The enclosing class contains all methods of all abstractions that are defined in the particular aspect, and therefore easily becomes bloated. In a way, this is a rather procedural style of programming, contradicting one of the fundamentals of object-oriented programming: A type definition contains all methods that belong to its interface. It also contradicts the aspect-oriented vision of defining crosscutting modules in terms of their own modular structure. The structure of the aspect in Listing 8-1 is one of empty abstractions and unstructured method definitions. As such, it is not particularly modular.

The implications of this design decision are not only conceptual but also practical. First, we cannot pass objects that play a role to other classes that expect an instance of that role. Envisage, for illustration, a role Comparable with a method compareTo(). If we want to pass an object as a Comparable to another class, e.g., a sorting class, then the approach in Listings 8-1 and 8-2 based on introducing an empty interface and encoding all methods as top-level methods of the enclosing class does not work. The alternative would be to use AspectJ's introduction mechanism to introduce the interface and its methods directly into the respective classes. However, doing that loses independent extensibility, as discussed previously. For example, a Point could be compared to another Point by means of their geometrical distance as well as their Manhattan distance |x| + |y| to the origin, which would require two independent implementations of the Comparable abstraction.

A similar problem shows up when there are interactions between the abstractions that build up the aspect's model of the worldSubject and Observer in our exampleis needed. The interaction in Listing 8-1 is very simple: A subject passes itself on, calling the notify method on each observer, but the parameter never gets used in the binding of the aspect in Listing 8-2. It is more realistic that observers would want more detailed information of the state change that actually happened on the subject's site. This would require some query methods in the interface of the subject. Using the AspectJ design "pattern" exemplified in Listings 8-1 and 8-2, where abstractions are type-less, we would have to declare such query methods also at the top level, e.g., getState(Subject s). The query methods would have to be declared abstract in Listing 8-1 since their implementation is binding-specific and should be implemented by the concrete binding subaspect in Listing 8-2. However, it is not possible to implement different query methods for Point and Line. That is, it is not possible to dynamically dispatch with regard to the type of the base objects being decorated with the subject functionality.

With the solution in Listings 8-1 and 8-2, it is also awkward to associate state with the individual abstractions in the definition of the aspect. For example, the observers of all subjects are stored in a global hash map perSubjectObservers. Besides the dangers of such a global bottleneck, the access and management of state becomes clumsy. The example in Listing 8-1 is relatively simple, because state is associated with only one of the abstractions (Subject), and this state consists of only one "field." However, the general case is that multiple abstractions in the module structure of the aspect may declare multiple fields. A simple example would be an implementation where observers maintain a history of the observed state change, e.g., when they need to react on sequences of events rather than on individual changes. If we consider the case that all roles need many different fields, then the code might very easily become a mess if all these fields are hosted by the outer aspect.

The problem with modeling state becomes worse when we consider the case of role inheritance, e.g., SpecialSubject inheriting from Subject. In this case, we would end up simulating shared data fields manually. This problem with modeling state applies to the aspect binding as well. There, we might want to associate state with the objects that are mapped to the aspect roles, for example, in order to cache computed values.

Summarizing the problems so far, what we would like to have is a nested class structure of aspect implementation and aspect binding within which we can assign methods and state to every aspect role in isolation.

8.2.2. Lack of Support for Sophisticated Mapping

The second kind of problem with the solution in Listings 8-1 and 8-2 is that the mapping from aspect abstractions to base classes by means of the declare parents construct works only when each aspect abstraction has a corresponding base class to which it is mapped directly. However, this is not always the case. For example, consider a scenario in which there is no class Line and every Point object has a collection of adjacent points. If we want to map this data structure to a graph aspect defined in terms of Node and Edge abstractions, then an edge would be represented by two adjacent points, but there is no abstraction in the base application to which we can map the Edge abstraction. The latter is only implicitly and indirectly represented by the collections of adjacent points.

8.2.3. Lack of Support for Reusable Aspect Bindings

Third, every aspect binding is coupled to one particular implementation. For example, the ColorObserver binding in Listing 8-2 is hardwired to the observer pattern implementation in Listing 8-1, although the binding itself is not dependent on the implementation details of the observer pattern. The observer pattern is not a very good example to illustrate the usefulness of a binding that can be used with many different implementations. A better example is that of an aspect binding that maps an arbitrary data structure, such as the classes of an abstract syntax tree, to a general tree representation. Many different implementations of a tree make sense in conjunction with such a binding, for example, one that displays trees on the screen or one that performs algorithms on trees. That is, one might want to be able to write some functionality that is parameterized with a particular binding type but that is polymorphic with respect to the implementation. However, this is not possible when the binding is coupled to the implementation.

8.2.4. Lack of Support for Aspectual Polymorphism

The fourth deficiency concerns aspect deployment. We say that the ColorObserver aspect in Listing 8-2 is statically deployed. By this, we mean that once compiled together with the package containing the figure classes, the changes in the particular points in the execution of point and line objects implied by the ColorObserver aspect are effective. It is not possible to determine at runtime whether to apply the aspect at all or which implementation of the aspect to apply, e.g., a LinkedList version or one with asynchronous notifications. We say that aspectual polymorphism is missing in the sense that the code is not polymorphic with respect to the types and implementations of the aspects affecting it after compilation.



Aspect-Oriented Software Development
Aspect-Oriented Software Development with Use Cases
ISBN: 0321268881
EAN: 2147483647
Year: 2003
Pages: 307

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net