AOP@Work: Enhance design patterns with AspectJ, Part 1

AOP makes patterns lighter, more flexible, and easier to (re)use

Design patterns have long been part of the experienced developer's tool chest. Unfortunately, because patterns can affect multiple classes, they can also be invasive and hard to (re)use. In this two-part article, the third in the AOP@Work series, Nicholas Lesiecki shows you how AOP solves this problem by fundamentally transforming pattern implementation. He examines three classic Gang of Four (GoF) design patterns (Adapter, Decorator, and Observer) and discusses the practical and design benefits of implementing them with aspect-oriented techniques.

Nicholas Lesiecki (ndlesiecki@apache.org), Software engineer/Programming instructor, Google

Nicholas Lesiecki is a recognized expert on AOP in the Java language. In addition to coauthoring Mastering AspectJ (Wiley, 2003), Nick is a member of AspectMentor, a consortium of experts in aspect-oriented software development. He has spoken about applying AspectJ to testing, design patterns, and real-world business problems in such venues as SD West, OOPSLA, AOSD, and the No Fluff Just Stuff symposium series. He currently serves Google as a Software Engineer and Programming Instructor.



17 May 2005

Also available in Russian

What is a design pattern? According to Design Patterns: Elements of Reusable Object-Oriented Software (commonly referred to as GoF; see Resources for details):

A design pattern systematically names, motivates, and explains a general design that addresses a recurring design problem in object-oriented systems. It describes the problem, the solution, when to apply the solution, and its consequences. It also gives implementation hints and examples. The solution is a general arrangement of objects and classes that solve the problem. The solution is customized and implemented to solve the problem in a particular context.

After years of successfully applying patterns to solve problems in OO systems, I found myself nodding along with this definition. Patterns were a great way to talk to my fellow programmers about design, and they represented best practices that addressed recurring design problems. So it was a bit of a shock to me when I attended a talk by Stuart Halloway where he suggested an alternate title for the GoF: "Workarounds for things that are broken in C++." His point was that what exists as a "pattern" in one language can be subsumed into the language itself in a different paradigm. He went on to give the example of Factories -- useful in the Java™ language, but less useful in Objective-C, which lets you return subtypes from a constructor.

I had to think about it for a while before I realized that the two sides were saying the same thing: design patterns give us a vocabulary for expressing concepts that can't be said directly in the programming language.

So where does AOP come in? For OOP, we have the GoF patterns, which give us a consistent, though sometimes cumbersome, way of working with common concepts like observers and decorators. AOP builds on OOP to give us a direct way of expressing crosscutting concerns. It turns out that some of the GoF patterns are about crosscutting and can be expressed directly in AOP. So what you'll notice is that some of the patterns that involve many classes can be expressed with a single aspect. Some patterns become easier to use because they involve less code. Some are so well supported that they almost disappear. Others are strictly tied to OOP (for example, the patterns dealing with class structures) and remain unchanged when used in conjunction with AOP.

About this series

The AOP@Work series is intended for developers who have some background in aspect-oriented programming and want to expand or deepen what they know. As with most developerWorks articles, the series is highly practical: you can expect to come away from every article with new knowledge that you can put immediately to use.

Each of the authors contributing to the series has been selected for his leadership or expertise in aspect-oriented programming. Many of the authors are contributors to the projects or tools covered in the series. Each article is subjected to a peer review to ensure the fairness and accuracy of the views expressed.

Please contact the authors individually with comments or questions about their articles. To comment on the series as a whole, you may contact series lead Nicholas Lesiecki. See Resources for more background on AOP.

This article explores pattern implementation with AOP (specifically AspectJ). I chose to tackle GoF patterns because of their widespread popularity and general utility. In this part of the article, I set up some criteria for analyzing the impact of patterns and then go on to investigate the Adapter and Decorator patterns. Adapter demonstrates the benefits of static crosscutting, while Decorator reveals itself as a disappearing pattern. In Part 2, I offer a more in-depth study of the Observer pattern, which does not disappear, but sees major benefits when implemented in AspectJ. Part 2 illustrates how AspectJ allows patterns to be converted into reusable base aspects, thus allowing you to download prebuilt pattern libraries -- an exciting prospect for the pattern enthusiast.

Why apply AOP to design patterns?

I previously claimed that many patterns are crosscutting, but of course I wasn't the first one to think of this. A recent research paper analyzed the GoF patterns and found that 17 out of the 23 patterns exhibited some degree of crosscutting. (The paper is Jan Hannemann and Gregor Kiczales's "Design Pattern Implementation in Java AspectJ," see the Resources section for more details.) If AOP promises to help with crosscutting, what kind of benefits can you expect to see from using it on design patterns? I'll start by answering that question in general terms, and then set up a framework through which I'll consider each design pattern.

Benefits of using AOP on design patterns

The first key benefit of AOP is the ability to localize the code for a given design pattern. This means that the pattern can often be realized in a single aspect or a pair of closely related aspects. (This stands in contrast to a Java-language implementation, where a pattern application can spread across several classes.) Being able to see all of the code in a single location leads to several practical benefits. First, readers of the code can understand the pattern more easily if all of its interactions are visible in one place. Second, if developers need to alter a pattern implementation, they can make that change in one place, instead of chasing the pattern fragments throughout the system. Third, developers can use meaningful names to describe the aspect that encapsulates the pattern, giving textual clues as to the intent of the pattern to future maintainers. For example you could name an aspect SensorAdapter, indicating that you had applied the Adapter pattern to sensors.

The other key benefit of an AspectJ pattern implementation is obliviousness. In other words, classes that play a role in the pattern do not necessarily have to be aware of that role. This benefit results directly from localization -- because the pattern is localized in an aspect, it does not invade its participants. Obliviousness makes the code for pattern participants easier to understand. Not only that, but obliviousness makes pattern composition a great deal easier. In a Java-language implementation, if a class participates in multiple patterns, the pattern machinery can quickly obscure its core meaning. Classes that do not know about their participation in a pattern can also be reused in other contexts. You'll see a concrete example of this in Part 2 of the article.

What is crosscutting?

Programs often exhibit behavior that does not fit naturally into a single program module, or even several closely related program modules. The aspect community describes this type of behavior as crosscutting because it cuts across the typical divisions of responsibility in a given programming model. In OO programming, for instance, the natural unit of modularity is the class, and a crosscutting concern is a concern that spans multiple classes. Thus, if a design pattern contributes behavior to three otherwise distinct classes, that design pattern can be said to crosscut those classes. Crosscutting leads to code scattering (related code does not localize with other related code) and code tangling (related code sits next to unrelated code). This scattering and tangling makes it difficult to reason about the system. For a refresher on AOP concepts like crosscutting, see the Resources section.

These benefits allow for code-level reuse of some patterns. The concepts and structure of design patterns have always been reusable. Anyone who wanted to implement the Observer pattern could pull out the GoF book and meld the pattern into their code. But with aspect-orientation, you can spare yourself the trouble by downloading the ObserverProtocol aspect (it's available at the Design Patterns project; check the Resources section). Beyond the saved implementation effort, code-level reuse also allows tighter coupling of pattern code and documentation. For example, I can browse the javadocs for ObserverProtocol and understand its intent and structure without having to pick up a separate textbook.

A framework for analysis

Each pattern description follows a common structure. I'll begin with an example problem and provide a general description of the pattern. Then I will describe how to implement the pattern, first in the Java language and then in the AspectJ language. After each implementation I will describe what makes the pattern crosscutting and what effects that version of the pattern has on understanding, maintaining, reusing, and composing the code.

Click the Code icon at the top or bottom of this article (or see Download) to download the complete source for upcoming examples.


The Adapter pattern

The first pattern I'll consider in detail is Adapter. Adapter is all about compatibility. The pattern allows classes to interact that otherwise couldn't because of incompatible interfaces. To implement Adapter in Java code, you wrap a class (the target class) with a special Adapter class that translates the API of the target class into one that clients expect or can more easily make use of.

The setup: Providing an aggregated sensor readout

Let's say you're designing a space vehicle, and you've been asked to provide a readout of all of the critical sensors in the spacecraft. Because you're extending an existing system, convenient Java classes already exist for each sensor you need to access. For example, you can access a temperature gauge with the following class:

public class TemperatureGauge {

  public int readTemperature(){
    //accesses sensor internals    
  }
}

You can access a radiation sensor with:

public class RadiationDetector {

  public double getCurrentRadiationLevel(){
    //read radiation somehow
  }
}

Some of these sensor classes were written by other team members, some by third-party vendors. What you would like to do is to provide a display of each sensor's status, so that a mission commander can see at a glance whether his craft is in trouble. Here's an example of what you're looking for. (The real display would probably involve flashing red lights and klaxons, but I'll stick to text for now.)

Readout:
Sensor 1 status is OK
Sensor 2 status is OK
Sensor 3 status is BORDERLINE
Sensor 4 status is DANGER!

You could accomplish a display like this using the following method:

public static void main(String[] args){
    RadiationDetector radiationDetector = //find critical detector
    TemperatureGauge gauge = //get exhaust nozzle gauge
    
    List allSensors = new ArrayList();
    allSensors.add(radiationDetector);
    allSensors.add(gauge);
    
    int count = 1;
    for (Sensor sensor : allSensors) {
      //How to read each type of sensor...?
    }
  }

So far, so good, but how do you read each sensor without resorting to ugly if(sensor instanceof XXX) checks? One option is to modify each sensor class to have a getStatus() method that interprets the sensor's readings and returns a String, as shown here:

if(this.readTemperature() > 160){
  return "DANGER";
}
return "OK"

Doing this introduces unrelated status-display code into multiple classes, adding to their complexity. Further, there might be practical limitations (such as having to recompile a third-party class). That's where the Adapter pattern steps in.


A Java-language Adapter

The traditional implementation of the Adapter pattern works by wrapping each target class with a class that implements a convenient API. In this case, you would create a common interface called, say, StatusSensor, shown here:

public interface StatusSensor {
  String getStatus();
}

With this common interface in place, you could implement the readout method like this:

  for (StatusSensor sensor : allSensors) {
    System.out.print("Sensor " + count++);
    System.out.println(" status is " + sensor.getStatus());  
  }

The only remaining challenge is to make each sensor conform to the interface. The Adapter classes accomplish this. As you can see in Listing 1, each Adapter stores the sensor it wraps in a member variable and uses that underlying sensor to implement the getStatus method:

Listing 1. Adapter classes and client code
//Adapter classes
public class RadiationAdapter implements StatusSensor {
  private final RadiationDetector underlying;

  public RadiationAdapter(RadiationDetector radiationDetector) {
    this.underlying = radiationDetector;
  }

  public String getStatus() {
    if(underlying.getCurrentRadiationLevel() > 1.5){
      return "DANGER";
    }
    return "OK";
  }
}

public class TemperatureAdapter implements StatusSensor {
  //...similar
}

//glue code to wrap each sensor with its adapter...
allSensors.add(new RadiationAdapter(radiationDetector));
allSensors.add(new TemperatureAdapter(gauge));

The listing also shows "glue code" that wraps each sensor with the appropriate Adapter before the readout. The pattern does not dictate that this glue code appear in any particular place. Likely locations include "just after creation" and "just before use." The sample code places it just before it adds the sensor to the readout collection.

Analysis of the Java language Adapter

The Adapter pattern fares reasonably well in its traditional implementation. What makes this crosscutting? Well, the "status" concern cuts across several different sensor classes. If you were to co-locate the Adapter classes in a package, the OO implementation of this pattern would modularize fairly well. The package would become an "Adapter Module." The wrapping idiom prevents the sensors from knowing about the pattern, which leads to looser coupling. Unfortunately, the part of the application that did the wrapping would need to know about both the Adapters and the sensors they applied to. So the glue code location would also be crosscut by the pattern.

Now, here's how the Java language Adapter stacks up against my evaluation criteria:

  • Understanding: Evocatively named SensorAdapters co-located in a package make the intent of this pattern clear. Unfortunately, the glue code's location might be far from the Adapter package. Since the glue code area is unstructured, you might overlook it while attempting to understand the pattern or trip over it while trying to understand the code it tangles with.

    You must also take care to deal with issues of object identity. That is, if wrapped and unwrapped versions of the same object coexist in a system, you will have to sort out whether they should be regarded as equal.
  • Reusing: To reuse this pattern, you must reimplement it from scratch.
  • Maintaining: As you add new sensors to the readout, you must add new adapter classes and update the glue code to wrap them.
  • Composing: Imagine you want to involve the sensors in another pattern. The sensors are oblivious to the Adapter, so they would be unaffected. However, this a two-edged sword. Should the new pattern consider the adapted or the unadapted version of the sensor to be its object?

An AspectJ Adapter

As with other design patterns, the AspectJ implementation of Adapter preserves the intent and concepts of its cousin. The implementation uses intertype declarations, an important type of crosscutting support that gets less airtime than pointcuts and advice. If you need a refresher on static crosscutting, check out the Resources section for appropriate pointers.

As with the pure OOP version, the AOP version of Adapter requires the StatusSensor interface. However, instead of using separate wrapper classes, the AspectJ version uses the declare parents form to make the various sensors implement StatusSensor directly, as you see here:

public aspect SensorAdapter {

  declare parents : 
    (TemperatureGauge || RadiationDetector) 
    implements StatusSensor;
}

Now the sensors should conform to the interface. But they do not yet implement the interface (a fact that the AspectJ compiler will happily point out to you). To complete the pattern implementation, you must add intertype method declarations to the aspect to make each sensor conform. The code below adds the getStatus() method to the TemperatureGauge class:

public String TemperatureGauge.getStatus(){
    if(this.readTemperature() > 160){
      return "DANGER";
    }
    return "OK";
  }

The AspectJ version of the readout class looks the same as the version implemented in the Java language, except that no glue code is necessary to wrap the sensors. Each sensor is its own wrapper.

Modifying third party code

Astute readers probably recall that in the setup for the example, I mentioned that one of the sensors came from a third-party vendor and that this would prevent you from easily adding a method to the class in the Java-language implementation. In contrast, the AspectJ compiler provides the ability to affect compiled bytecode in order to allow developers to apply aspects to a wider range of classes. Adding a compilation step in which the SensorAdapter aspect was woven into the third-party jar would let you use the adapted version of the class seamlessly.

Some developers may worry about the ramifications of modifying bytecode without possessing the corresponding source code. In some cases, this sort of modification violates license terms (for example, it's widely understood that modifying classes provided by Sun Microsystems violates the corresponding license). In other cases, creating invasive modifications to a class without a solid understanding of its internals could introduce subtle bugs. In this case, however, the modifications serve only to supplement the functionality of the target class and interact only with its public interface. Assuming that the provider of RadiationDetector does not forbid modification to its classes, there's no more reason to be concerned about the AOP version of Adapter than there is about the OOP version.

Analysis of the AspectJ Adapter

The key benefit of implementing Adapter with AspectJ is localization. The entire pattern is contained within a single aspect, rather than in two (or more) separate adapter classes and an unstructured "wrapping" location. Here's how the AspectJ implementation fares according to my criteria:

  • Understanding: The lack of wrapping removes the need to look anywhere other than the Adapter aspect to understand the pattern. No wrapping also removes the need to deal with object identity issues.
  • Reusing: The AspectJ version isn't any more or less reusable than the other version.
  • Maintaining: Because each new sensor involves only writing a method (rather than a full class), extending the AspectJ implementation should be slightly easier. As the number of adaptees grows or the logic required for each adaptation becomes complex, you may find that a single aspect becomes unreasonably long. In this case, you can split the aspect into several. Splitting the aspect loses some of the localization benefits, but preserves the other benefits.
  • Composing: You can compose multiple patterns easily because there are no wrapping-coordination issues to deal with.

The upshot is that both the Java and AspectJ implementations do a good job of staying out of the way of the sensor classes. However, only the AspectJ version stays out of the rest of your application. Is this a major advantage? It probably depends on whether your application exhibits any of the complicating properties described in the analyses. If I were using AspectJ on a project, I would definitely use it to implement Adapter, although I wouldn't introduce AspectJ just to solve this problem. The next pattern, Decorator, provides some more compelling advantages.


The Decorator pattern

Decorator is an interesting pattern to consider from an aspect-oriented perspective because it's one of the patterns that comes closest to "disappearing" with the capabilities introduced by an aspect-oriented language such as AspectJ. Why is this? A closer look at how Decorator evolves in both aspect- and object-oriented implementations should make it clear.

The Decorator pattern aims to add capabilities dynamically to an existing object. The canonical example given in the GoF book involves literal decoration. In their example, a GUI component class is wrapped in a decorator class that adds a border, or perhaps scrollbars, to the display of the component. The Java Class Libraries feature Decorator prominently, with the methods on java.util.Collections that decorate a Collection so that it becomes unmodifiable or synchronized, as well as a rich variety of IO streams that can buffer, inflate, or monitor other streams.

The setup: Monitoring file reads

To add spice to this example, I chose to stick with the Decorators in the Java distribution and see what it would take to replicate one of them in AspectJ. One of the decorators I found intriguing was ProgressMonitorInputStream from javax.swing. According to the documentation, ProgressMonitorInputStream monitors the progress of reading an underlying input stream.

To demonstrate decoration in action, I've written a simple GUI that reads a file. You can view the code for reading the input stream in Listing 2 and see screenshots of the running app in Figure 1. (You can also play with the full source of the example by clicking on the Code icon at the top or bottom of this page.)

Figure 1. The ProgressMonitor for the stream
The ProgressMonitor for the stream

You may wish to have the javadocs or indeed the source code for java.io and ProgressMonitorInputStream open beside you as you consider the next section; see Resources for further reference.


A Java language Decorator

In the Java language, you realize the Decorator pattern by first creating an AbstractComponent interface (or class) that both base implementations (sometimes referred to as ConcreteComponents) and decorators can conform to. In this example, the AbstractComponent is java.io.InputStream, which defines the interface both for FileInputStream (the ConcreteComponent) and BufferedInputStream (a ConcreteDecorator).

Although it is not strictly necessary, Decorator implementations often feature an AbstractDecorator that maintains a reference to the decorated component and provides the basic decoration mechanics without adding any additional behavior. In java.io, FilterInputStream provides this functionality. Finally, a ConcreteDecorator extends the AbstractDecorator, overrides methods requiring decoration, and adds behavior before or after invoking the same method on the decorated component. In this case, ProgressMonitorInputStream plays the ConcreteDecorator.

Glancing at Sun's implementation of ProgressMonitorInputStream (which I won't reprint here because of licensing concerns), you can see that it instantiates a javax.swing.ProgressMonitor upon creation. After every read method, it updates the monitor with a count of how many bytes have been read from the underlying stream. The separate ProgressMonitor class determines when to pop up a monitoring dialog and updates the visual display.

To use ProgressMonitorInputStream, you simply need to wrap another input stream (as in Listing 2) and be sure to refer to the wrapped instance when doing reads. Notice the similarity here between the Adapter and Decorator patterns: both require programmatic application of the additional behavior to the target class.

Listing 2. Monitoring an InputStream
private void actuallyReadFile() {
  try {

    InputStream in = createInputStream();
    byte[] b =new byte[1000];
    while (in.read(b) != -1) {
      //do whatever here
      bytesRead+=1000;
    }
    bytesReadLabel.setText("Read " +  (bytesRead/1000) + "k");
    bytesRead = 0;
    in.close();
  } catch (Exception e) {
    //handle...
  }
}


private InputStream createInputStream() throws FileNotFoundException   
{
  InputStream stream = new FileInputStream(name.getText());
  stream = new BufferedInputStream(stream);
  
  //_this_ is a JPanel GUI component 
  stream = new ProgressMonitorInputStream(
       this, "This is gonna take a while", stream);
  return stream;
}

Analysis of the Java language Decorator

From looking at the example, it may seem that nothing could be easier than using the Decorator pattern. However, don't forget that to make the example work, Sun implemented InputStream, FilterInputStream, and ProgressMonitorInputStream -- a non-trivial amount of code.

In the example, the concern of monitoring crosscuts the InputStream. More generally, the decoration crosscuts the decoration target. Further, the decoration concern may cut across the application. For example, users could demand a ProgressMonitor for all file inputs. (Lest you think this is an artificial example, ask yourself how many times you've used an input stream without buffering it.)

Now let's look at the remaining criteria:

  • Understanding: Once you know Decorator is at work, it's fairly easy to comprehend. But I'll never forget the confusion I felt the first time I cracked open java.io and tried to make sense of the wealth of machinery classes that made up Decorator as applied to streams. Although a quick tutorial could have easily set me straight, there was no easy way to arrive at a comprehension of the pattern just from looking at the code. A more concrete measure of comprehension burden is lines of code. I'll take a look at line counts after examining the AspectJ implementation. It's also worth noting that, because it uses wrapping, Decorator suffers from the same object identity issues that affect Adapter.
  • Reusing: To reuse this pattern implementation, you must reimplement it.
  • Maintaining: There are a couple of key maintenance scenarios. In the first, you add a new decorator to an existing implementation. Depending on the number of methods in the Decorator, this could be tedious, but it's certainly not hard. In the second scenario, you add a new operation to the AbstractComponent (that is, InputStream). This means updating all of the existing decorators to reflect the new operation and making a decision at each one whether the decoration should apply to the new method.
  • Composing: Because the decorators and the component share a common interface, Decorator allows for transparent composition of decorators on a given instance. (Just look at Listing 2, where the code buffers and monitors the input stream.) This is great, especially because the decoration targets do not have to be aware of being decorated.

An AspectJ Decorator

In their paper, Hanneman and Kiczales state that

Using AspectJ, the implementation of some patterns completely disappears, because AspectJ language constructs implement them directly. This applies to [Decorator].

Taking a look at the Motivation section for Decorator in the GoF book, it becomes obvious why this would be the case:

The decorator forwards requests to the component and may perform additional actions (such as drawing a border) before or after forwarding. Transparency lets you nest decorators recursively, thereby allowing an unlimited number of additional responsibilities.

What is advice after all, but the ability to transparently add additional "actions" to any operation? In a sense, AspectJ lets you decorate any method. To see how this plays out in a real system, you can examine the monitoring of input stream reads in AspectJ.

Identifying decorated operations

To correctly implement monitoring, the aspect must identify read operations on the stream. You do this by writing a pointcut that picks out read operations:

pointcut arrayRead() :
    call(public int InputStream+.read(..));

Now you can apply some advice of the following general form:

after() returning (int bytesRead) :
    arrayRead()
  {
    updateMonitor(bytesRead);
  }

This advice uses the returning form to expose the return value of the method call. The number of bytes read is then fed to a private method on the aspect: updateMonitor(). This method takes care of the details of updating the actual ProgressMonitor (more on this later).

Exposing relevant context

The solution so far is simple. However, it turns out that the ProgressMonitor class requires a few more things to do its job. Specifically, it needs a GUI component to tie the monitoring dialog to. You saw this requirement in the traditional implementation:

//this is a JPanel GUI component 
stream = new ProgressMonitorInputStream(this, "This is gonna take a while", stream);

To obtain the GUI component it needs, the aspect must bind it in the pointcut so that it can be used by the advice. Listing 3 contains the revised pointcut and advice. Notice that the fromAComponent() pointcut makes use of the primitive cflow() pointcut. In essence, the pointcut is saying "select all join points that occur as a result of the execution of a method on a JComponent and expose that component for use in advice."

Listing 3. Ferrying context to the monitor using cflow
pointcut arrayRead(JComponent component, InputStream is) :
    call(public int InputStream+.read(..)) && target(is)
    && fromAComponent(component);
  
  pointcut fromAComponent(JComponent component) : 
    cflow(execution(* javax.swing.JComponent+.*(..))
      && this(component));

after(JComponent component, InputStream is) returning (int bytesRead) :
  arrayRead(component, is)
{
  updateMonitor(component, is, bytesRead);
}

Maintaining state

To make the aspect widely applicable (and to accurately mimic the other implementation), the aspect must maintain state. That is, it should pop up a unique progress monitor per monitored stream. AspectJ offers several options for dealing with this. The best choice for this aspect is probably to maintain per-object state storage using a Map. This technique will reappear in my implementation of the Observer pattern, so take note! (Other ways to store state specific to an object include intertype declarations and pertarget/perthis aspects, but considering these concepts is beyond the scope of this article.)

To implement state storage, you first declare a WeakHashMap that takes a stream as a key and stores a monitor as a value. You want to use a WeakHashMap because WeakHashMaps will not prevent their keys from being garbage collected if the keys are no longer in ordinary use. This best-practice prevents the aspect from holding references to defunct objects and thereby creating a memory-leak.

The updateMonitor() method then uses the map to lazily initialize a new IncrementMonitor. Once the method is sure the monitor exists, it updates it with the latest progress (indicated by the return value of read()). Listing 4 shows the code for the lazy initialization and progress updates, as well as the full code for IncrementMonitor:

Listing 4. Lazy initialization of per-stream monitor
  //From the aspect...
  private void updateMonitor(JComponent component, InputStream is,
           int amount){
    IncrementMonitor monitor = 
      (IncrementMonitor)perStreamMonitor.get(is);
    if(monitor == null){
      monitor = initMonitor(is, component);
    }
    monitor.increment(amount);
  }
  
  private IncrementMonitor initMonitor(InputStream is,
                 JComponent component){
    try {
      int size = is.available();
      IncrementMonitor monitor = 
        new IncrementMonitor(component, size);
      perStreamMonitor.put(is, monitor);
      return monitor;
    } catch (Exception e) {
      //...handle
    }
  }
  
}//...end aspect

public class IncrementMonitor extends ProgressMonitor{

  private int counter;
  
  public IncrementMonitor(Component component, int size){
    super(component, "Some Title", null, 0, size);
  }
  
  public void increment(int amount){
    counter += amount;
    setProgress(counter);
  }
}

Finally, the aspect needs to discard the monitor when the stream has been fully read. If you're thinking in aspects at the moment, you'll recognize the opportunity. InputStream conveniently defines a close() method for the aspect to advise, as shown here:

before(InputStream is): 
    call(public void InputStream+.close())
    && target(is)
  {
    System.out.println("Discarding monitor.");
    perStreamMonitor.remove(is);
  }

At this point, you've completed the exercise. If you're familiar with the implementation of InputStream, however, you'll have spotted something I deliberately left out. The read() method (no parameters) must be handled differently from the other readmethods because its return value is not the number of bytes read, but rather the next byte in the stream. The example code that accompanies this article expands the aspect to deal with this constraint, but I urge you to visualize how you would address this concern if you were writing the aspect before referring to the code.

Analysis of the AspectJ Decorator

Like Adapter, the two implementations of Decorator differ in localization. In the AspectJ version, the entire pattern sits neatly in a single aspect. (I've excluded the IncrementMonitor helper class because it does not play a structural role in the pattern.) In the Java language version, the base pattern implementation (leaving aside the client code) requires three classes. What effects does this have?

  • Understanding: Because of the power of AspectJ's pointcut language, an aspect can affect multiple operations with the same advice. In contrast, a decorator class must repeat the behavior at each operation. In part because of this, Sun's implementation shows more than twice as many lines of code as the aspect implementation. I counted approximately 110 lines for the ProgressMonitorInputStream and 40 for FilterInputStream (I'll leave out InputStream since it could be a legitimate superclass absent the Decorator pattern). In contrast, the MonitorFileReads aspect consumes 53 lines, and the IncrementMonitor helper class consumes 12. The line ratio stands at 160 to 65, or about 2.4 to 1. Although lines-of-code (LOC) is a crude measure, in general shorter code is clearer code.

    Furthermore, if you are familiar with AOP, the AspectJ solution does not give you the sense that something special is going on. The Java language solution requires several classes working carefully in concert, while the AspectJ version looks as if it's doing what most aspects do: adding behavior to a set of join points through advice.

    Finally, it's worth remembering that a frequent criticism of AOP is that you can "no longer tell what a module is doing by reading the source." If you apply decorators to objects without the help of aspects, there's no source-based clue in either the client code (other than the wrapping location) or the decoration target (the FileInputStream) that the object displays additional behavior. In contrast, if you examine the GUI from Listing 2 in AJDT, you will see a friendly annotation on the line while (in.read(b) != -1) that indicates that the monitoring aspect affects the read call. The combination of AspectJ and its development environment provides better information in this case than the original implementation.
  • Reusing: Because decoration is built into the language, almost all aspects reuse this pattern. A more specific reuse would be to make the monitoring aspect abstract and allow subaspects to specify a pointcut for monitored operations. In this way, nearly any object could be decorated with monitoring -- without the preparation required by the traditional implementation. (If you're wondering about abstract aspects, Part 2 of the article explains their use in more detail.)
  • Maintaining: Adding a new decoration to an object requires no special effort. If the decoration target changes (imagine a new type of read method), then you must (possibly) update the pointcuts to account for this. Having to update a pointcut is burdensome, but the burden can be reduced by writing robust pointcuts that are likely to catch new operations. (See the Resources for a link to a great blog entry on robust pointcuts.) In any case, updating a pointcut seems less trouble than updating all the decorators as would be required for a similar change in the Java-language implementation.

    Here's another interesting scenario (mentioned earlier in the Java language analysis): monitoring all file reads. With an OO decorator, this means that every class that reads a stream must remember to wrap it in a ProgressMonitorInputStream. In contrast, the MonitorFileReads aspect will monitor reads on any input stream as long as they occur from within the control flow of a JComponent. Because ProgressMonitor only ever pops up when the operation is taking longer than a preset threshold, this aspect could transparently ensure that users never get annoyed at having to wait for a file read -- without the need for programmer vigilance.
  • Composing: Like the competing implementation, the AspectJ version allows for transparent composition of multiple decorators with low effort.

As I've mentioned before, Decorator's chief trick (transparently adding behavior to an operation) is subsumed by the AspectJ language. The only challenge for the AspectJ implementation is how to associate aspectual state (the updated progress monitor) with a specific instance -- the example used a map to make this association. This need to handle the association preserves Decorator as a pattern in AspectJ. Sometimes, when the decoration machinery already exists, it seems easier to use a traditional Decorator -- especially because the pattern does not invade the decorated class. However, if the decoration machinery does not exist, the flexibility and simplicity of the AspectJ implementation make a better choice.


Conclusion to Part 1

I hope that this tour of two familiar patterns has helped illustrate aspect-oriented mechanisms in practice. As the development community grapples with the ramifications of an emerging paradigm, it can be useful to apply the new technology to old problems -- problems for which good solutions already exist. The exercise can provide a familiar vantage from which to asses the new approach.

So how has it fared so far? While it's not a golden hammer, AspectJ has managed to secure some solid advantages when used to implement traditional OO patterns. These advantages stem from AspectJ's ability to better handle crosscutting concerns. By gathering the code for a pattern into a single aspect, AspectJ makes it easier to understand the pattern from reading the code. Because pattern code does not show up in non-pattern classes (such as the wrapping locations required by Adapter and Decorator) these other classes are also easier to understand. The combination also makes it easier to extend and maintain the system, and even to reuse the patterns elsewhere.

Adapter and Decorator represent medium-complexity patterns. In Part 2 of this article, I'll examine whether aspect-orientation scales to more complex patterns. Specifically, Part 2 tackles Observer, a pattern that involves multiple roles and dynamic relationships. Part 2 also explores aspect-oriented reuse -- the ability to define a pattern or protocol as an abstract aspect and to apply it with an application-specific aspect.


Download

DescriptionNameSize
Code samplej-aopwork56code.zip142 KB

Resources

  • Click the Code icon at the top or bottom of this article (or see Download) to download the source code discussed in this article.
  • AOP@Work is a year-long series dedicated to helping you incorporate AOP into your day-to-day Java programming. Don't miss a single article in the series. See the complete series listing.
  • If you need an introduction to AspectJ and AOP, check out the introductory article "Improve modularity with aspect-oriented programming" (developerWorks, January 2002).
  • For more information on the AspectJ development environment (AJDT), take a look at and this offering from members of the AJDT team: " Develop aspect-oriented Java applications with Eclipse and AJDT" (developerWorks, September 2004).
  • The section on Decorator suggests that you take a look at the sources for java.io. Sun provides an implementation: J2SE 1.4.2.
  • Learn more about the Gang of Four and their book at Hillside.net.
  • The Aspect-Oriented Design Pattern Implementations project has so far refactored 23 GoF patterns using AspectJ; three of them are studied in this article.
  • Project authors Jan Hannemann and Gregor Kiczales have made reusable abstract aspects from 13 of the studied patterns freely available under the Mozilla Public License.
  • For a more in-depth study, see Hannemann and Kiczales's "Design Pattern Implementation in Java AspectJ" (OOPSLA, November 2002), which proposes that AspectJ implementations of the GoF design patterns show modularity improvements in 17 of 23 cases.
  • Adrian Colyer's The Aspects Blog brings you up to the minute news from the AspectJ project combined with penetrating design insights and practical applications of aspect technology. This entry discusses how to write robust pointcut expressions .
  • The Portland Pattern Repository is an excellent resource for learning about patterns, as well as a great introduction to the patterns community. It includes sections on the Adapter, Decorator, and Observer patterns.
  • You'll find articles about every aspect of Java programming in the developerWorks Java technology zone.
  • Browse for books on these and other technical topics.
  • Also see the Java technology zone tutorials page for a complete Listing of free Java-focused tutorials from developerWorks.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=83210
ArticleTitle=AOP@Work: Enhance design patterns with AspectJ, Part 1
publish-date=05172005