Section 24.3. ASPECT-ORIENTED ALTERNATIVES TO DESIGN PATTERNS


24.3. ASPECT-ORIENTED ALTERNATIVES TO DESIGN PATTERNS

Many established patterns for object-oriented design (even those with dependency management as a key objective) deserve to be revisited in light of aspect-oriented alternatives or improvements.

24.3.1. Visitor

The first design pattern to succumb to aspect-oriented replacement is the Visitor pattern [7] with its infamous cyclic dependency between visitor and element hierarchy, illustrated in Figure 24-4. The improper dependency from Visitor to ConcreteElement violates all three dependency direction principlesit goes from abstract to concrete, goes from stable to less stable, and forms a cycle. The author has been among those who have proposed object-oriented solutions to this problem [11, 14]. However, the aspect-oriented solution is basic to AOSD and much more effective (see Figure 24-5).

Figure 24-4. The Visitor pattern shows an obvious flaw when plotted on an abstractness/instability field with all dependencies explicitly shown.


Figure 24-5. The aspect-oriented solution to extending the behavior of existing classes moves the "weaving" across an element hierarchy that was accomplished by the Visitor pattern from user code to programming language and compiler. This eliminates Visitor's deadly cyclic dependency from the source code.


Interestingly, object-oriented solutions to the cyclic Visitor dependency problem remove the "good" dependencies from Element and ConcreteElement to Visitor. The bad dependency noted in Figure 24-4 from Visitor to ConcreteElement is what makes the pattern what it is. In an aspect-oriented language, where an aspect can augment any class, the good dependency is in a sense left inevery class has a built-in dependency on the language feature of augmentability. The bad dependency, on the other hand, disappears from programmer consciousness (there is no user source code defining what classes may be augmented), though it might be said to remain in the compiler writer's consciousness. (Analogous statements could have been made about polymorphism during the transition from procedural to object-oriented languages.)

Whether the implementation technology is "open classes," "introductions," "composition," or "hyperslices," the ability to expand the behavior of an entire class hierarchy after the fact (without changing the original code) is, even by itself, a welcome advance in the design toolbox. When this ability is combined with the ability to augment behavior already present in the hierarchy, even more new doors open, and many old techniques may need rethinking.

24.3.2. Observer

The case against Visitor is easy to make. More surprising is the possibility of replacing the workhorse Observer pattern [7]surprising because the Observer pattern's whole purpose is to invert or avoid dependencies. For example, Figures 24-6 and 24-7 show the "before" and "after" of applying the Observer pattern to a temperature sensor that notifies a display when the temperature changes. The naEFve solution of Figure 24-6 is a design in which the sensor is directly wired to the temperature display in order to trigger display changes. After adding the Subject and Observer classes of the Observer pattern (see Figure 24-7), this hardwiring is gone, dependencies are all in the right direction, and the code for TemperatureSensor is pulled in the direction of greater reusability.

Figure 24-6. Without the Observer pattern, a temperature sensor keeps a hard-wired reference to a temperature display in order to notify it of temperature changes. This "bad" dependency is in opposition to the ADP, DIP, and SDP.


Figure 24-7. Adding the Observer pattern not only removes the improper dependency but also pulls some of the code in the direction of greater reusability (the upper-right corner).


The Observer pattern sees use in many applications, particularly in large graphical user interfaces and in message-oriented middleware. A specialized form of Observer is at the heart of Document-View or Model-View-Controller patterns in almost every GUI framework. Why would we want to improve on such a venerable design workhorse? How could an aspect-oriented solution do any better? Table 24-2 lists problems with the Observer pattern and their possible aspect-oriented solutions.

Table 24-2. Problems with the Observer Pattern Are Surprisingly Many But May Have These Aspect-Oriented Solutions

Problem

Solution

The designer of an event source must decide ahead of time what events will be needed when and with what passed state.

The "obliviousness" inherent in aspect-oriented languages [5] means that an event source designer need not even recognize it as such.

The sender of an event must be designed with the appropriate choice of push or pull and level of detail for changed state information.

Different event receivers can weave different levels of detail into the same event sender code.

Notifications are difficult to accomplish in a single satisfactory thread-safe way.

Clients with different threading models can define aspects with different thread awareness.

Some clients may prefer bundling events for performance or for non-flickering screen updates.

The bundling of events need not be the responsibility of event source or sink but of a connective aspect between them.

Distributed event notification requires much more sophisticated mechanisms for event propagation.

A connective aspect can define the distribution model on top of the event detail, leaving sender and receiver both independent of distribution details.

Registration and corresponding unregistration can be difficult to get right.

Registration and unregistration can appear nearby in a single notification aspect with appropriate related pointcuts or composition rules.

Program comprehension can be difficult when there is a lack of documentation relating to which objects observe which others.

An aspect can declare both the senders and the receivers of an event in a succinct fashion.


Sadly (for reference-chasing programmers), object-oriented solutions to the problems of Table 24-2 would most likely add another level of indirection. For example, adding a local proxy Observer that receives ordinary events and then publishes them over the network can solve the problem of distributed event sending. Unfortunately, each level of indirection moves the software farther from the real world or analysis-level view of the problem and deeper into relatively artificial mechanism classes that add overhead to both design comprehension and implementation debugging.

Figure 24-8 shows a simple aspect-oriented replacement for Observer. The notification is now orchestrated by a single aspect that weaves itself into TemperatureSensor in order to notify TemperatureDisplay of changes. One immediate benefit is greater reusability for TemperatureSensor because all notification code is externalized, freeing clients in different applications to employ different notification interfaces or none at all. (Recall that reusability is greatest in the upper-right corner of the abstraction/change penalty graph.) TemperatureDisplay is also more reusable because it no longer needs any knowledge of TemperatureSensor. It could easily be wired to different temperature sensors by different aspects, even if one sensor has a method named getTemp() and another has a method named getTemperature().

Figure 24-8. An aspect-oriented alternative to the Observer pattern is a notification aspect that more directly accomplishes the wiring between two objects while decoupling them completely and making each more reusable.


There is another way in which the design illustrated in Figure 24-8 is an improvement over Figure 24-7. The techniques and benefits of "aspect-oriented analysis" are mostly yet to be discovered, but notice that the classes and aspects of Figure 24-8 more closely match the physical model that one might conceive for this application: a temperature sensor, a display, and a wire from one to the other. Object-oriented designs tend to become littered with mechanism classes, classes that serve a critical software function but that have no correspondence to real world objects. With aspect-oriented notification, at least, the software module that captures notification itself has a counterpart in the real world (wire, post office, semaphores, kick in the pants, etc.). This seems like a satisfying outcome. As with the aspect-oriented answer to Visitor, the artificial mechanism classes are gone (Visitor, Observer, Subject).

In some cases, the requirements for the notification might include functionality like asynchronous or idle-time firing of notifications, queuing and elimination of redundant notifications, etc. In fact, it is easy to generate a list of aspects of event propagation:

  1. State change propagation

  2. Thread safety

  3. Bundling of related or redundant events

  4. Distribution

  5. Delivery guarantees

  6. Causality

  7. Event tracing

  8. Debugging aids

When one or more of these apply, the mechanism classes may be worth keeping but may still be orchestrated by a notification aspect. The AspectJ Tutorial includes an example of applying the Observer pattern in its simplest form to classes that do not in themselves provide notifications [1]. That approach could be readily extended to more sophisticated Subject and Observer base classes or, better still, to simple Subject and Observer classes augmented with multiple aspects from the preceding list. Figure 24-9 illustrates the concept of a mixed object-oriented/aspect-oriented Observer design.

Figure 24-9. A combined aspect-oriented and object-oriented Observer design has the potential to decouple notification from both sender and receiver and also to decouple multiple aspects of the notification (like thread safety, debugging aids, and notification bundling) from the base Observer implementation and from one another.


24.3.3. Extension Object

The Extension Object pattern [6] combines the dependency inversion characteristics of the Observer pattern with the extensibility characteristics of the Visitor pattern. Figure 24-10 shows the structure of this pattern in Abstraction/Change Penalty space. A concrete base class, Subject, provides mechanisms to dynamically add and query for extensions. A given extension must be downcast to its specific interface (SpecificExtension) for use. The pattern as a whole amounts to defining a standard way to cross cast from one interface reference to another.

Figure 24-10. The Extension Object pattern includes mechanism classes (Subject and Extension) to invert the dependency between an original class and its extensions or between partial interfaces to a concrete whole.


The aspect-oriented alternative to extension objects is extension aspects (see Figure 24-11). This approach eliminates the need for client cross casting from one extension (or base interface) to another by directly injecting the implementation of a specific interface into a class seen by a given set of clients. Besides removing the cross casting from client code, this approach reduces the run-time overhead of the cross cast and the design complexity as measured in number of classes and methods. The core functionality is more reusable since its extension mechanism is not predefined via inheritance from Subject and dependence upon Extension.

Figure 24-11. An aspect-oriented extension to an existing class simply augments that class to directly or indirectly realize the desired specific interface, eliminating the run-time cross casting mechanism of the Extension Object pattern.


An interesting detail is whether the client(s) of the specific extension should be dependent upon the aspect or whether the aspect should be dependent upon the client(s). The second option would lead to a design more like the notification aspect of Figure 24-8 but with a wider connection between client and service. The first option is probably more appropriate if there are several clients or the clients are unknown at compile time. A judgement of the relative stability and abstractness of the client(s) and the extension they make use of would answer the question. Different approaches could even be taken for different extensions of the same core class.

24.3.4. Virtual Construction

As a final example, this section describes the use of a factory for polymorphic object construction, which is another design in which object-oriented indirection can be combined with aspect-oriented indirection to improve upon a common design pattern or programming idiom.

The starting point is the simple Factory pattern shown in Figure 24-12. This common idiom for "virtual construction" includes a level of indirection between a client and a set of polymorphic services, one of which is the construction of a product from any given string description of what kind of product is needed. This pattern works nicely for XML deserialization and other situations where objects are to be constructed in memory from their persistent representations. There are two related problems with this pattern:

  1. The Factory class depends upon every concrete Product type and is therefore subject to repeated maintenance.

  2. The selection of what kind of product to construct is inappropriately separated from the products themselves.

Figure 24-12. Factories are a common idiom for "virtual construction" of polymorphic objects.


An aspect-oriented Factory solves these problems and comes closer to the goal of virtual construction: products that polymorphically assemble themselves from given assembly instructions. Figure 24-13 shows the aspect-oriented design, and Listing 24-1 includes the full code of an AspectJ [1, 10] implementation. This solution retains all the elements of the purely object-oriented solution but adds two aspects related to product manufacturing. The object-oriented hinge point, IProduct, continues to provide value not only for polymorphic construction but polymorphic behavior after construction. The Factory class still appears but no longer knows how to make anything (see Factory.makeProduct() in Listing 24-1). Instead, a VirtualConstruction aspect located inside each product class performs product selection and weaves itself into Factory with the help of a pointcut defined in aspect ProductManufacturing.

Figure 24-13. An aspect-oriented factory moves the product selection code into the product modules and therefore comes closer to the goal of "virtual" construction.


Here are the practical benefits of aspect-oriented virtual construction:

  1. When a new product is added, only a single file needs to be added and editedthe code for the product itself.

  2. A product may be temporarily or permanently removed without commenting or deleting a block of Factory code.

  3. The constants that determine product kind need not be exposed outside each concrete product.

  4. There is no big switch statement in the Factory.

These benefits derive from the ability of aspect code to compose itself into a base functionality without any modification of that base functionality. (In this case the base factory has no capability in itself.) This design approaches the ultimate in flexible manufacturingan empty factory floor that can be reconfigured at will.

Listing 24-1. An aspect-oriented factory, implemented in AspectJ, moves the product selection code inside the concrete product code, eliminating many of the troubles with maintaining factory code in its object-oriented form
 interface IProduct {   void beUseful();   void becomeObsolete(); } class Factory {   public IProduct makeProduct( String whatKind )   {     return null;  // aspects required!   } } aspect ProductManufacturing {   static pointcut production( String whatKind ):     target(Factory) &&     args(whatKind) &&     call(IProduct makeProduct(String)); } class ProductA   implements IProduct {   // product "self-assembly" occurs here   private static aspect VirtualConstruction   {     IProduct around(String whatKind):       ProductManufacturing.production(whatKind)     {       if ( whatKind.equals( "A" ) )       {         return new ProductA();       }       else       {         return proceed(whatKind);       }     }   }   ProductA()   {     System.out.println("new ProductA");   }   public void beUseful()   {     System.out.println("ProductA.beUseful");   }   public void becomeObsolete()   {     System.out.println("ProductA.becomeObsolete");   } } // Similar code for ProductB ... class FactoryTest {   public static void main( String args[] )   {     Factory factory = new Factory();     IProduct productA = factory.makeProduct("A");     productA.beUseful();     productA.becomeObsolete();   } } 

The fourth benefit in this list, switch statement elimination, is often a benefit ascribed to object polymorphism. The VirtualConstruction aspect eliminates the switch statement left over in the object-oriented solution. In a sense, the aspects of this design cancel out the negative effects of the factory/product indirection by moving construction code back into the various product modules and reversing the dependencies from factory to product.

The aspect idiom of Listing 24-1 incorporates clear aspect-oriented indirection. The pointcut (when the aspect applies) is defined separately from the behavior to be incorporated at that pointcut. In this case, the "when" is defined first in one place, and the "what" is defined separately in multiple places. This is an interesting twist on the usual case of an abstract library aspect that defines the "what" in one place and requires concrete aspects to define the "when," usually in multiple places.



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