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.
The code for DeceleratingBall is pretty straightforward: a constructor and a move method:
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.
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.
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:
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.
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?
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:
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!)
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.
Read this short summary of the idea of a decorator, including when it is useful.
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:
- We want to add features to individual balls in our ball games or to individual card piles in a set of card games.
- We want to add features to readers and writers in the Java library, such as buffering the input we read using a reader.
- We want to add windowing features to individual objects in a word processor or drawing program.
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
- Create a "decorator" using composition to encapsulate an instance of the base class as an instance variable.
- Implement the new behavior in the decorator. Delegate as much of the new behavior to the instance variable as possible.
- Send all other messages recursively to the encapsulated object.
- Use inheritance to make the decorator a subclass of the base class.
The idea of a decorator occurs in the real world, too, far away from computer programs. Consider how we "decorate" a picture:
Notice the similarities in those "class diagrams".
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!
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?