Lasagne/J and the Decorator Pattern

 

The Decorator Pattern

The Decorator Pattern is intended to “Attach additional responsibilities to an object dynamically”. ["Design Patterns - Elements of Reusable Software" by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides].

 

The motivating example of the Decorator pattern is a graphical user interface toolkit, that lets you add properties like borders or behaviors like scrolling to any user interface component by enclosing the component in another objects that add the border or the scroll capability. The enclosing object is called a decorator. The decorator conforms to the interface of the component it decorates so that its presence is transparent to the component's clients. The decorator forwards requests to the component and may perform additional actions (such as drawing a border) before or after forwarding. Transparency allows you to nest decorators recursively, thereby allowing an unlimited number of added responsibilities.

 

 

In the Decorator pattern the ScrollDecorator and BorderDecorator classes are subclasses of Decorator, an abstract class for visual components that decorate other visual components. VisualComponent is the abstract class for visual objects. It defines their drawing and event handling interface. Decorating is simply done by having the Decorator class forwarding draw requests to its component, and let the Decorator subclasses extend this operation. The figure below shows the class structure of the Decorator pattern.

 

 

Decorator subclasses can add additional operations for specific functionality. For instance, a ScrollDecorator can add a ScrollTo operation that lets other objects scroll the textview if they know there happens to be a ScrollDecorator object in the interface. An important aspect of the Decorator pattern is that it lets decorators appear anywhere a VisualComponent can. That way, clients generally can't tell the difference between a decorated component and an undecorated one, and so they don't depend at all on the decoration.

 

Using Wrappers Instead of the Decorator Pattern

Lasagne/J allows a developer to decorate an object, without introducing the need for neither a new class nor an additional object. The decorator code is dynamically superimposed by a generic wrapper on the methods of the decorated object. This superimposition allows a software designer to avoid the problems of object identity, lack of common self, and Dopplegängers inherent in use of the Decorator Pattern.

 

Implementation

 

A decorator that adds a scrollbar to any subclass of VisualComponent can now be defined as follows:

 

package org.lasagnej.examples.designpatterns.decorator.scrollbar;

 

wrapper ScrollDecorator wraps VisualComponent

{

   public void draw()

   {

      System.out.println("ScrollDecorator::draw()");

      inner.draw();

   } 

}

 

and similar for a border decorator

 

package org.lasagnej.examples.designpatterns.decorator.border;

 

wrapper BorderDecorator wraps VisualComponent

{

   public void draw()

   {

      System.out.println("BorderDecorator::draw()");

      inner.draw();

   } 

}

 

If you want to apply a border around a TextView object this is simply done with class widening. Context sensitive class widening of an object is effectuated by constructive downcast of the object reference through which the object is referenced in that particular context. Repeating the class widening progress one more time on the same object reference allows us to apply a border decorator as well. The source code below exemplifies this procedure.

 

public class Main {

   public static void main(String[] args)

   {

      TextView textView = new TextView();

      textView.draw();

      // let's have a text view with a scrollbar!

      textView = (TextView)((ScrollDecorator)textView);

      textView.draw();

      // let's add a border as well!

      textView = (TextView)((BorderDecorator)textView);

      textView.draw();

   } 

}

 

Consequences

 

Some of the benefits and liabilities of using Lasagne/J when you need to decorate an object are as follows:

 

  1. Similar to the Decorator pattern, Lasagne/J provides a more flexible way to add responsibilities to objects than could ever be achieved with static (multiple) inheritance. With decorators, responsibilities can be added and removed at run-time simply by attaching (class widening) and detaching (class lifting) them. In contrast, inheritance requires creating a new class for each additional responsibility (e.g., BorderedScrollableTextView, BorderedTextView). This gives rise to many classes and increases the complexity of a system. Furthermore, providing different Decorator wrappers for an abstract class lets you mix and match responsibilities for its subclasses as well.
  2. As is the case with the Decorator pattern, Lasagne/J offers a pay-as-you-go approach to adding responsibilities. Instead of trying to support all foreseeable features in a complex, customizable class, you can define a simple class and add functionality incrementally with generic wrappers. Functionality can be composed from simple unrelated wrappers or by specialization of wrappers. As a result, an application needn't pay for features it doesn't use. It is also at least, just as easy to define new kinds of Decorators independently from the classes of objects they extend, even for unforeseen extensions, as it is with the Decorator pattern. Extending a complex class tends to expose details unrelated to the responsibilities you're adding.
  3. In contrast to the Decorator pattern you can rely on object identity when you use Lasagne/J wrappers to decorate an object.