Session 25

Making Balls Do More


CS 2530
Intermediate Computing


Opening Exercise: A Real Client!

The physics department would like for us to write a simple "ball world" program that it can use to teach the idea of friction. In this program, we need for some Balls to decelerate. Every time one of these decelerating balls moves, its speed decreases by 1%.

Write a DeceleratingBall class for this purpose.

If you need a new accessor method, assume that I have written it for you.



A Simple Solution

The code for DeceleratingBall is pretty straightforward: a constructor and a move method:

ball hierarchy, after adding DeceleratingBall

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

We create a DeceleratingBall just as we would any other Ball:

    Ball b = new DeceleratingBall( 10, 15, 5, 5.0, 10.0 );
as in this new MultiBallWorldFrame.

After adding th new subclass, we have the Ball hierarchy you see at the right.



A New Wrinkle

ball hierarchy, after adding DeceleratingBoundedBall

The department head in Physics is so happy with your program that he comes back for more. (No, his name is not Arnold.) In other simulations, some decelerating balls need to bounce of the walls they hit, too. So we need a class of BoundedBalls that decelerate.

Make it so.

That is an easy problem to solve! The code for DeceleratingBoundedBall is nearly identical to DeceleratingBall. The same code works for our new move() method. All we have to change is the constructor which, because the ball is bounded, must be given the frame in which the ball lives.



How Good is Our Solution?

Our approach to this family of problems is simple enough. We can implement a decelerating version of any Ball class that needs a decelerating counterpart.

What are the strengths of this approach?

What are the weaknesses of this approach?

You may be asking yourself... So what? It works.

But: What happens if we need to change the deceleration factor, say, from 1% to 2%?

We must remember to make the change
in two different classes.

But: What happens if we need to add deceleration behavior to other Balls? We have already created at least three other Ball classes:

ball hierarchy, with other classes showing

We have to write more subclasses!

But: What happens if we need to add another kind of behavior to our ball classes, including the decelerating balls?

We have to write even more subclasses!

Solutions that make future extensions to a program unbearable are usually not very good solutions at all.



An Alternative Solution

BoundedBalls respond to the same set of messages as Balls.

So they are substitutable for one another.

Can we use this to our advantage?

Yes! Take a look at this solution. Instead of extending a ball class and inheriting its behavior, DeceleratingBall has a Ball instance variable to which it delegates all requests.

We create a DeceleratingBall by giving it a Ball to direct:

    DeceleratingBall b = new DeceleratingBall(
                             new Ball( 10, 15, 5, 2.0, 5.0 ) );

How does the new DeceleratingBall work?

now, a decelerating ball delegates to another ball

This approach uses the substitutability to our advantage. It creates an object that delegates most of its responsibilities to another object... without the client knowing the difference. It adds behavior by sending the delegate other messages, also without the client knowing the difference.

You can verify this for yourself by creating a DeceleratingBall by giving it another kind of ball to direct, say, a BoundedBall:

    DeceleratingBall b = new DeceleratingBall(
                             new BoundedBall( 10, 15, 5, 2.0, 5.0, this ) );

Our new ball hierarchy looks like this:

the decorated ball hierarchy

Note one other change I had to make. DeceleratingBall extends Ball, so that it can be plugged into a Ball variable, or any slot that expects a Ball. The new class ignores the instance variables and methods that it inherits, instead using its delegate to do its work. But as a subclass, the DeceleratingBall constructor must invoke a Ball constructor to initialize the inherited state. (The compiler does not know that we intend to ignore it!)

So, I added a default constructor to the Ball class that allows us to satisfy that requirement without worrying about the details of the inherited IVs.

(This is another hint that our approach would work even better with an interface than a superclass!)



How Good is Our New Solution?

What are the weaknesses of our new approach?

What are the strengths of our new approach?

We sometimes choose a more complex solution when it offers extensibility and flexibility as benefits.



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:

a 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 Ball 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 ..... November 13, 2012