Session 24

The Decorator Pattern


810:062
Computer Science II
Object-Oriented Programming


Opening Exercise

After Session 23, our Ball hierarchy looks like this:

ball hierarchy with decelerating ball as decorator

Add an ExpandingBall class to the Ball hierarchy. An ExpandingBall increases its radius by one pixel every time it moves. Use the delegation technique we learned for the DeceleratingBall class last time.


The Solution

We add our new subclass in the same way as before: by delegating to another Ball.

Here is how we might use an ExpandingBall in the MultiBallWorld:

    public MultiBallWorldFrame( Color ballColor )
    {
        super();

        setSize ( FrameWidth, FrameHeight );
        setTitle( "Ball World" );

        ballArray = new BoundedBall[ BallArraySize ];
        for (int i = 0; i < BallArraySize; i++)
        {
            ballArray[i] = new ExpandingBall(
                               new BoundedBall(
                                   10, 15, 5, 3.0+i, 6.0-i, this)));
        }
        counter = 0;
    }

We can also make an ExpandingBall that delegate to a ShadowBall:

            ballArray[i] = new ExpandingBall(
                               new ShadowBall(
                                   10, 15, 5, 3.0+i, 6.0-i ) );

By delegating to a Ball, our ExpandingBall class works with any class in the Ball hierarchy. That, my friends, is polymorphism at work.


Going All the Way

Just a second, though... A DeceleratingBall is a Ball, too. Shouldn't we be able to make an expanding, decelerating ball? Let's try it...

... we try it, with no success ...

What's going on here? Remember how our delegation technique works:

decelerating ball delegates

Then take a look at the move() methods in our DeceleratingBall and ExpandingBall classes:

    public void move()           // IN EXPANDING BALL
    {
        workerBall.move();
        workerBall.growBy( 1 );
    }

    public void move()           // IN DECELERATING BALL
    {
        workerBall.move();
        workerBall.adjustSpeedBy( -0.01 );
    }

ExpandingBall delegates not only a move() message, but also a growBy() message. A DeceleratingBall knows to delegate the move() message onto it worker ball, but not the growBy() message. But the worker ball is the ball being painted, so it needs to be the ball to grow by a pixel.

This points out one flaw in our design: DeceleratingBall and ExpandingBall need to delegate all of the messages they receive, even the ones sent via protected routes.

So, we add to DeceleratingBall protected delegation methods for all of the methods it inherits from Ball and Disk. We do the same to ExpandingBall

Now, we are able to execute this beautiful example of what the delegation technique can do for us:

    for (int i = 0; i < numberOfBalls; i++)
    {
        int dx = (int) (20 * Math.random() );
        int dy = (int) (10 * Math.random() );
        ball[i] = new ExpandingBall(
                      new DeceleratingBall(
                          new BoundedBall( 100, 100, 10, dx, dy, this ) );
    }

decelerating ball delegates

Amazing. Because one of our "delegators" is substitutable for instances of its superclass, we can delegate to another object that delegates! This, my friends, is polymorphism at work.


Do You Recognize a Pattern?

We added flexibility and extensibility to our set of classes using the same ideas we have used over and over in previous programs: composition and inheritance.

The new twist in this solution is that DeceleratingBall and ExpandingBall use composition and inheritance at the same time, with the same class!

This shows how substitutability can make programs easier to write.

This new twist is so common that it has its own name:

decorator

Read this mini-chapter to learn more about decorators.


Another Exercise

Our Ball hierarchy now looks like this:

our decorated ball hierarchy

But if we look at our code, we see that the DeceleratingBall class looks a lot like the ExpandingBall class.

We have violated the "Say it once and only once" rule!

Duplicated code was the motivation for the Decorator pattern in the first place, and using it has created more.

Your job: Eliminate the duplication.


A Simple Solution

This is a common problem when extending a class hierarchy. We add a new class that looks just like an existing one, with a small change. Duplicating code is a reasonable thing when we are trying to implement a new behavior -- it's often the easiest thing to do. But we don't want to have to maintain duplicated code over time.

Common problems tend to have common solutions. The solution to this problem isn't really new to us -- we factor the common behavior into a superclass:

Extract Superclass refactoring

Here is how to extract the superclass:

1. Create a DecoratedBall class that implements the behavior common to our two decorators.

    public class DecoratedBall extends Ball
    {
        private Ball workerBall;

        public DecoratedBall( Ball aBall ) { ... }

        protected Ball worker()
        {
            return workerBall;
        }

        // ** DELEGATE ALL MESSAGES TO THE INSTANCE VARIABLE!
        ...
        public void  move ()   { workerBall.move(); }
        ...
     }

With class, programmers who want to implement a particular kind of Ball decorator do not have to implement any of the default behaviors, only the behaviors that they want to change.

2. Change each of the decorator classes to be a subclass of DecoratedBall.

For example:

    public class DeceleratingBall extends DecoratedBall
    {
        public DeceleratingBall( Ball aBall )
        {
            super( aBall );
        }

        public void move()
        {
            super.move();
            adjustSpeedBy( -0.01 );
        }
    }

All of the DELEGATED METHODS are inherited!


Evaluating the DecoratedBall Class

The DecoratedBall class offers the same benefits of the DeceleratingBall and ExpandingBall classes implemented using the Decorator pattern:

The new Ball hierarchy looks like this:

refactored ball hierarchy

More generally, this is how we implement the Decorator pattern:

generalized Decorator pattern


The Effect of the Decorator Pattern

Our first DeceleratingBall from Session 23 (as a simple subclass):

    public class DeceleratingBall extends Ball 
    {
        public DeceleratingBall( int x, int y, int r, double dx, double dy )
        {
            super( x, y, r, dx, dy );
        }

        public void move()
        {
            super.move();
            adjustSpeedBy( -0.01 );
        }
    }

Our final DeceleratingBall after refactoring (as a subclass of DecoratedBall):

    public class DeceleratingBall extends DecoratedBall
    {
        public DeceleratingBall( Ball aBall )
        {
            super( aBall );
        }

        public void move()
        {
            super.move();
            adjustSpeedBy( -0.01 );
        }
    }

Notice: our new DeceleratingBall class
looks just like the one we wrote before using the Decorator pattern!

A good design pattern will do that...


Wrap Up


The Decorator Pattern

The Problem

We would like to add a behavior to a set of classes that share a common interface.

This problem is prevalent. It occurs in many domains and in many applications. Here are a few:

A Tempting Solution that Fails

Use inheritance to create a new class of objects that has the behavior.

Use instances of this class when you need the behavior, and use instances of the superclass otherwise.

This solution is impractical. Why?

We will need to create multiple subclasses and replicate the behavior in each.

What if we would like to add more behavior to the extended object? We have to make (many!) more subclasses!

The Solution

  1. Create a "decorator" using composition to encapsulate an instance of the base class as an instance variable.
  2. Implement the new behavior in the decorator. Delegate as much of the new behavior to the instance variable as possible.
  3. Send all other messages recursively to the encapsulated object.
  4. Use inheritance to make the decorator a subclass of the base class.

decorator class diagram

The idea of a decorator occurs in the real world, too, far away from computer programs. Consider how we "decorate" a picture:

real-world decorator

Notice the similarities in those "class diagrams"...


How Does the Decorator Pattern Work?

This is an example of using inheritance and composition together.

Inheritance creates substitutable classes. This allows a decorated object to be used in all the same places as the encapsulated object!

The superclass acts as an interface for all of its subclasses.

An application won't know -- or need to know -- what sort of MovableBall it is using. The ball responds to all the same messages.

Composition uses substitution to reuse the code in the base class, but in a way that is controlled by the decorator.

This allows us to add the same behavior to all of the classes in the hierarchy!


When Should You Use a Decorator?

In a way, the decorator pattern allows us to add a new behavior to a single instance, rather than to the whole class.

This works well whenever the new behavior is orthogonal to the existing behaviors. A behavior is orthogonal if it is related to the existing behaviors but isn't like any one of them. When two behaviors can both occur, or neither occur, or occur one without the other, then those behaviors are orthogonal to one another.

For example, we cannot point to a particular place in the Ball hierarchy and say, "That's where deceleration belongs!" Deceleration 'cuts across' the Ball class hierarchy.

Using a decorator makes sense any time there are two varieties of some object, but the variations are not directly related. For example, balls that bounce off the walls and balls that decelerate.

Would implementing BoundedBall as a decorator be a good idea?


Eugene Wallingford ..... wallingf@cs.uni.edu ..... April 5, 2005