Design patterns - Decorator

From Juneday education
Jump to: navigation, search

Description

Aim of the pattern

In this chapter, we'll discuss the Decorator pattern. The aim of this pattern is to solve the problem of enriching objects with special characteristics which can be combined, without creating a class explosion (too many classes). It also lets us change the capabilities of an object in runtime.

Problem description

Let's say we have a class describing an object of some type. We discover that at certain times, it would be nice to have objects which can do a little more than is described in the class, or doing things differently. We further discover that we would like to be able to combine these special treats.

If we were to use inheritance for this, we'd end up in creating a whole lot of classes. The reason is that combining different treats can be done in many ways.

Consider the java.util.Collection type. The designers realized that it would be handy if a Collection could be "checked" for type safety. It would also be handy if a Collection could be synchronized (thread safe). And it would also be handy if a Collection could be immutable (unmodifiable). But all those treats should be both optional and possible to combine.

If the designers would have implemented this using inheritance, two things would happen. First, the type of Collection must be decided at compile time, so a client would have to make up its mind before compiling (unless we'd create some kind of adapter translating between the types). Second, the number of classes needed to express all these combinations of treats would quickly grow. Imagine the following sub interfaces to Collection:

  • CheckedCollection
  • SynchronizedCollection
  • UnmodifiableCollection
  • CheckedSynchronizedCollection
  • CheckedUnmodifiableCollection
  • CheckedSynchronizedUnmodifiableCollection

As you see, three treats can be combined in 6 ways (1x2x3 = 6). Now, consider the sub interfaces! We'd also need

  • CheckedList
  • SynchronizedList
  • UnmodifiableList
  • .....

And what about Set?

Solution - applying the Decorator pattern

Instead of doing something stupid like this, the designers opted for a more flexible solution - the possibility to decorate an object in runtime, using a wrapping object of a decorating class. Wrapping an object means to have a class of the same type as an object (like Collection), which holds a reference to an object of the same type, and forward all method calls to the wrapped object.

A DecoratedWithXCollection (implements Collection) could hold a reference to a Collection internally, and forward all calls of the overriden methods to the internal Collection object.

The collections api uses helper methods for this in the Collections class, like for instance Collections.unmodifiableList().

The IO API classes in java.io also use the decorator pattern. For instance, an InputStream would be nice to be able to take on the treat of buffering input. To accomplish this, we'd write code using a Buffer decorator class like this:

InputStream input =  new BufferedInputStream
             ( new FileInputStream("/home/someuser/file.txt") );

The abstract InputStream input is an object of type FileInputStream, which is wrapped inside the decorator object of type BufferedInputStream. Now, the input object is in fact an object of type FileInputStream but decorated with the treat of being able to buffer input from the file.

Solution with regards to the adventure game

In the video lecture, we take the above approach to our text-based adventure game. We hope you haven't grown tired of it yet.

The use case for our game is the idea of being able to decorate any WeaponBehavior (like a Sword or a Shotgun). The reason is that the game designers decide that there should exist magic in this game. Any weapon could be put under various magic spells, which would transform the effect of the weapon. They decided that there should at least exist two kinds of weapon spells:

  • Double injury spell (doubles the damage value of the useWeapon() method)
  • Double speed spell (doubles the use of useWeapon() so that it actually ís called twice, also resulting in double damage of course)

The spells should be possible to combine. That means, a Sword which gets both spells, not only doubles its damage points, it will be used twice when the useWeapon() method is called. This would in effect quadruple the damage value (from merely 15 to the quite impressive 60 damage points!).

This is what the WeaponDecorator class would look like:

public abstract class WeaponDecorator implements WeaponBehavior {

  private final WeaponBehavior decoratedWeapon;

  public WeaponDecorator(WeaponBehavior weapon) {
    this.decoratedWeapon = weapon;
  }

  @Override
  public int useWeapon() {
    // Forward the call to the decorated weapon
    return decoratedWeapon.useWeapon();
  }

  @Override
  public String toString() {
    return decoratedWeapon.toString();
  }
}

Note how it wraps a WeaponBehavior whose reference is passed via the constructor. Also, note how it in itself implements WeaponBehavior. Also, note how it forwards every method it implements (or otherwise overrides) to the wrapped WeaponBehavior object.

This is what the WithDoubleInjury decorator for the double injury spell would look like:

public class WithDoubleInjury extends WeaponDecorator {

  public WithDoubleInjury(WeaponBehavior weapon) {
    super(weapon);
  }

  @Override
  public int useWeapon() {
    System.out.println("Using " + this + " with double injury");
    return 2 * super.useWeapon();
  }

  @Override
  public String toString() {
    return super.toString();
  }
}

Note how this class overrides the useWeapon() method by doubling the result of the parent class implementation. The parent class (WeaponDecorator) simply forwards the call to the wrapped weapon. The effect is that if you wrap a Sword inside a WithDoubleInjury, calling useWeapon() will produce the double damage than the original Sword would, but still call the original (wrapped) Sword's useWeapon() method for the normal behavior.

This is the code for the double speed spell decorator:

public class WithDoubleSpeed extends WeaponDecorator {

  public WithDoubleSpeed(WeaponBehavior weapon) {
    super(weapon);
  }

  @Override
  public int useWeapon() {
    System.out.println("Using " + this + " with double speed - two times!");
    return super.useWeapon() +  super.useWeapon();
  }

  @Override
  public String toString() {
    return super.toString();
  }
}

How does these classes help client code? Let's see how a Sword is affected by first a double injury spell, then a double damage spell and finally a combination of both spells:

WeaponBehavior sword = new Sword();
int damage;
damage=sword.useWeapon();
System.out.println("Damage from " + sword + " " + damage);
System.out.println("============");

System.out.println("Player gets a double injury spell!");
sword = new WithDoubleInjury(sword);
damage=sword.useWeapon();
System.out.println("Damage from " + sword + " " + damage);
System.out.println("============");

// Re-set sword to normal Sword
sword = new Sword();

System.out.println("Player gets a double speed spell!");
sword = new WithDoubleSpeed(sword);
damage=sword.useWeapon();
System.out.println("Damage from " + sword + " " + damage);
System.out.println("============");

// Re-set sword to normal Sword
sword = new Sword();

System.out.println("Whoa! Double spell! Speed and injury!");
sword = new WithDoubleSpeed(new WithDoubleInjury(sword));
damage=sword.useWeapon();
System.out.println("Damage from " + sword + " " + damage);

/*
This is the output from the simulation:
$ java TestWeapon 
The sword shines and cuts through the air before it hits its target
Damage from Excalibur 15
============
Player gets a double injury spell!
Using Excalibur with double injury
The sword shines and cuts through the air before it hits its target
Damage from Excalibur 30
============
Player gets a double speed spell!
Using Excalibur with double speed - two times!
The sword shines and cuts through the air before it hits its target
The sword shines and cuts through the air before it hits its target
Damage from Excalibur 30
============
Whoa! Double spell! Speed and injury!
Using Excalibur with double speed - two times!
Using Excalibur with double injury
The sword shines and cuts through the air before it hits its target
Using Excalibur with double injury
The sword shines and cuts through the air before it hits its target
Damage from Excalibur 60
*/

Exercises

One of the senior programmers in the "Adventure game project" (by accident, also one of the guys claiming that "No-one wants a 3D-graphics first shooter game! Text-based adventure games is the thing, dude!"), who incidently also is a hippie, demands that there should be a "Peace and Love Spell" as well in the game. The "Peace and Love Spell" should convert a weapon's damage to a health injection. That is, a weapon with damage level 15 should give 15 health credits to the person attacked with the weapon. The damage is reversed, so attacking an opponent with a weapon under this spell with actually give the same amount of health to the attacked character as the weapon normally would inflict as damage.


Task 1 - Write the PeaceAndLoveWeaponDecorator and test it

Write a new decorator for WeaponBehavior (extending WeaponDecorator) which is called PeaceAndLoveWeaponDecorator which decorates a weapon in such a way that the damage is reversed. Write test code showing that this worked.

Challenge - Make the spell non-reversible

Make sure that applying the spell twice should not reverse the reversed damage! No matter how many times the spell is applied, it should always give health, never take damage.

Hints

Hint for task 1: In the overridden useWeapon() method, return the normal damage but negated (change the sign of the damage)

Hint for challenge: To make the spell not reversing an already inflicted weapon dangerous again, use a boolean instance variable in the class PeaceAndLoveDecorator to keep track of whether the spell is already set As star student Jozsef pointed out - this is a bad idea, because it will only work the first time. His suggestion is way better: When calculating the damage, use Math.abs().

Hint 2 for challenge: Use a method for checking whether there is already a spell in place. In order to find this out, you need to traverse all the wrapped weapons. Below is a snippet for how to traverse all wrapped weapons.

// For this to work, you need to make WeaponDecorator.decoratedWeapon
// either protected or package private (if the classes are in the same package)
WeaponBehavior w = super.decoratedWeapon;
while(! (w instanceof PeaceAndLoveWeaponDecorator)) {
  if(w instanceof WeaponDecorator){
    // let w be the next wrapped weapon
    w = ((WeaponDecorator)w).decoratedWeapon;
  }else{
    // we've reached the innermost weapon - it's the only one not
    // being a WeaponDecorator
    // break the loop
  }
}
// here, if w instanceof PeaceAndLoveWeaponDecorator, it was already wrapped!

Suggested solutions

Expand using link to the right to see the full content.

This could be one implementation of the PeaceAndLoveWeaponDecorator:

public class PeaceAndLoveWeaponDecorator extends WeaponDecorator {

  public PeaceAndLoveWeaponDecorator(WeaponBehavior weapon) {
    super(weapon);
  }

  @Override
  public int useWeapon() {
    System.out.println("Peace, Dude! Healing you with love!");
    // Jozsef's idea (quite smarter than Rikard's stupid idea)
    return 0 - Math.abs(super.useWeapon());
  }

  @Override
  public String toString() {
    return super.toString();
  }

}

A test showing that applying the spell twice doesn't reverse the spell is shown here:

public class TestPeaceAndLove {

  public static void main(String[] args) {
    WeaponBehavior sword = new Sword();
    int damage;
    System.out.println("Normal use of " + sword);
    damage=sword.useWeapon();
    System.out.println("Damage from " + sword + " " + damage);
    System.out.println("============");

    System.out.println("Player gets Peace and Love spell!");
    sword = new PeaceAndLoveWeaponDecorator(sword);
    System.out.println("Using the weapon with the spell in place");
    damage=sword.useWeapon();
    System.out.println("Damage from " + sword + " " + damage);
    System.out.println("============");

    System.out.println("Spell wears off. Back to normal.");
    sword = new Sword();
    System.out.println("Normal use of " + sword);
    damage=sword.useWeapon();
    System.out.println("Damage from " + sword + " " + damage);
    System.out.println("============");

    System.out.println("Player gets a Peace and Love spell");
    sword = new PeaceAndLoveWeaponDecorator(sword);
    System.out.println("Using the weapon with the spell in place");
    damage=sword.useWeapon();
    System.out.println("Damage from " + sword + " " + damage);
    System.out.println("============");

    System.out.println("Testing: Applying spell again (second time)");
    sword = new PeaceAndLoveWeaponDecorator(sword);
    damage=sword.useWeapon();
    System.out.println("Damage from " + sword + " " + damage);
    System.out.println("============");

    System.out.println("Testing: Applying spell again (third time)");
    sword = new PeaceAndLoveWeaponDecorator(sword);
    damage=sword.useWeapon();
    System.out.println("Damage from " + sword + " " + damage);
    System.out.println("============");

    System.out.println("Testing: Applying spell again (fourth time)");
    sword = new PeaceAndLoveWeaponDecorator(sword);
    damage=sword.useWeapon();
    System.out.println("Damage from " + sword + " " + damage);
    System.out.println("============");
  }
}

Alternative solution:

WeaponDecorator.java:

public abstract class WeaponDecorator implements WeaponBehavior {

  final WeaponBehavior decoratedWeapon;

  public WeaponDecorator(WeaponBehavior weapon) {
    this.decoratedWeapon = weapon;
  }

  @Override
  public int useWeapon() {
    // Forward the call to the decorated weapon
    return decoratedWeapon.useWeapon();
  }

  @Override
  public String toString() {
    return decoratedWeapon.toString();
  }

  // Creates a string with all the layers of wrapped weapons
  String getWrappedList() {
    StringBuilder result = new StringBuilder(this.getClass().getName());
    WeaponBehavior w = decoratedWeapon;
    result.append(" containing a ").append(w.getClass().getName());
    while(w instanceof WeaponDecorator) {
      w = ((WeaponDecorator)w).decoratedWeapon;
      result.append(" containing a ").append(w.getClass().getName());
    }
    return result.toString();
  }
}

PeaceAndLoveWeaponDecorator.java

public class PeaceAndLoveWeaponDecorator extends WeaponDecorator {

  public PeaceAndLoveWeaponDecorator(WeaponBehavior weapon) {
    super(weapon);
    System.out.println("Created a " + getWrappedList());
  }

  @Override
  public int useWeapon() {
      int damage;
      if (! isAlreadyWrapped()) {
        System.out.println("Peace, Dude! Healing you with love!");
        damage =  0 - super.useWeapon();
      } else {
        //System.out.println("Already wrapped inside a PeaceAndLove, using weapon as is");
        damage = super.useWeapon();
      }
      return damage;
  }

  private boolean isAlreadyWrapped() {
    WeaponBehavior w = super.decoratedWeapon;
    while(! (w instanceof PeaceAndLoveWeaponDecorator)) {
      if (w instanceof WeaponDecorator) {
        w = ((WeaponDecorator)w).decoratedWeapon;
      } else {
        break;
      }
    }
    return w instanceof PeaceAndLoveWeaponDecorator;
  }

  @Override
  public String toString() {
    return super.toString();
  }
}

The method isAlreadyWrapped() works like this:

  1. assign w the super class' decoratedWeapon
  2. while the wrapped weapon isn't a PeaceAndLoveWeaponDecorator, keep looking
    1. if w still is a WeaponDecorator, let w be its own decoratedWeapon
    2. otherwise we've found a normal weapon, so break
  3. return whether w is a PeaceAndLoveWeaponDecorator

The loop is entered, because there is a wrapped weapon which is not a PeaceAndLoveWeaponDecorator. If the wrapped weapon indeed is a PeaceAndLoveWeaponDecorator, the loop is skipped. The final return will then be true.

If the loop is entered, we check if the wrapped weapon w is a decorated weapon. If it is, we assign w to its wrapped weapon. If it isn't, we've must have found the innermost original weapon (like an undecorated Sword for instance). Then we should break the loop (or we'd enter an eternal loop) because we have reached the original weapon.

Chapter links

Videos

Bonus slide

Source code

Books this chapter is a part of

Further reading

Book TOC | previous chapter | next chapter