Decorator Pattern in Plain Speak

I've just had enough of the abstract high-faluting language used in the GOF's Design Patterns. Absolutely enough. I'm sure there's more than enough readers who can't understand any one of the GOF's denser patterns.
The Decorator Pattern is one such dense connundrum. I mean- just give it to me straight what does it do? why use it? how to build it?

In plain terms, if there is a need to add functionality to a class instance (object)- without changing its inherent behavior, then it's a scenario to use a Decorator pattern.

For adding functionality to a class, why not use inheritance or adding the class into another class thru aggregation or composition? All of these methods achieve the goal of adding new functionality to a base class- however this is all done at compile time.

In proper software development circles, it is more appropriate to stay flexible and nimble- by delaying the implementation until the last possible moment.
This flexibility is usually achieved by means of configuration (think Spring)- that wires up final implementation classes at runtime.

Likewise, the Decorator pattern can add new functionality to individual objects- rather than classes at runtime. In plain street English, Decorators are "Wrappers". Decorator classes wrap around a base class- and add new functionality.

GOFers usually diagram what they mean by means of UML. But how about just a simple Ascii diagram for passing on simple ideas without the exact ER formalisms???

In a Decorator pattern, there are four main classes.

  • An AbstractWidget class.
  • A ConcreteWidget class.
  • A AbstractWrapper derived from AbstractWidget, wraps around a AbstractWidget in constructor.
  • A ConcreteWrapper firmly adds new functionality to the wrapped Widget.

In simple psuedo code terms-

AbstractWidget {
  abstract baseMethod();
}

ConcreteWidget : AbstractWidget {
  @Override
  baseMethod();
}

AbstractWrapper : AbstractWidget {
  AbstractWrapper( AbstractWidget )
  @Override
  baseMethod();
}

ConcreteWrapper : AbstractWrapper {
  ConcreteWrapper( AbstractWidget )
  @Override
  baseMethod();
}

To put this pattern into practical terms, let's imagine two types of automobiles- HondaAccord and TeslaRoadster, each sold with two types of options- SunRoof and TintedWindows. Each combination of options- none, SunRoof only, TintedWindows only, SunRoof + TintedWindows- yields a unique final assembly for the car.

We can model each car + options combination with a base car class and a set of boolean flags. The problem with this model is that if we add a new option, we'd have to mess with the base car class. The Decorator allows us to add new options to a car without changing the base car class.

The code sample below shows one possible Decorator implementation.

// --------------------------------------------
// CarMake
// The Abstract Widget
//
package drills;

public abstract class CarMake {

    abstract public String getName();

    CarMake() {
    }            
}


// --------------------------------------------
// HondaAccord
// The concrete widget
//
package drills;

public class HondaAccord extends CarMake {

    public HondaAccord () {
    }

    @Override
    public String getName() {
        return "HondaAccord";
    }        
}


// --------------------------------------------
// TeslaRoadster
// Second concrete widget
//
package drills;

public class TeslaRoadster extends CarMake {

    public TeslaRoadster() {
    }

    @Override
    public String getName() {
        return "TeslaRoadster";
    }        
}


// --------------------------------------------
// CarDecorator
// Abstractor decorator (wrapper)
//
package drills;

public abstract class CarDecorator extends CarMake {

    @Override
    public CarDecorator(CarMake c) {
    }    
}



// --------------------------------------------
// SunRoofCarDecorator
//
package drills;

public class SunRoofCarDecorator extends CarDecorator {

    CarMake _wrappedCar;

    public SunRoofCarDecorator(CarMake c) {
        super(c);

        _wrappedCar = c;
    }

    @Override
    public String getName() {
        return _wrappedCar.getName() + ", SunRoof";
    }
}


// --------------------------------------------
// TintedWindowsCarDecorator
//
package drills;

public class TintedWindowsCarDecorator extends CarDecorator {

    CarMake _wrappedCar;

    public TintedWindowsCarDecorator(CarMake c) {
        super(c);

        _wrappedCar = c;
    }

    @Override  
    public String getName() {
        return _wrappedCar.getName() + ", TintedWindows";
    }
}


// --------------------------------------------
// Tester
//
package drills;

public class Tester {

    public static void main(String[] args) {

        // Car A, HondaAccord has nothing
        // Car B, HondaAccord has SunRoof
        // Car C, HondaAccord has TintedWindows
        // Car D, HondaAccord has SunRoof and TintedWindows

        // Car E, TeslaRoadster has nothing
        // Car F, TeslaRoadster has SunRoof
        // Car G, TeslaRoadster has TintedWindows
        // Car H, TeslaRoadster has SunRoof and TintedWindows

        CarMake a = new HondaAccord();
        CarMake b = new SunRoofCarDecorator(a);
        CarMake c = new TintedWindowsCarDecorator(a);
        CarMake d = new SunRoofCarDecorator(c);

        CarMake e = new TeslaRoadster();
        CarMake f = new SunRoofCarDecorator(e);
        CarMake g = new TintedWindowsCarDecorator(e);
        CarMake h = new TintedWindowsCarDecorator(f);

        System.out.println("A = " + a.getName());              
        System.out.println("B = " + b.getName());              
        System.out.println("C = " + c.getName());              
        System.out.println("D = " + d.getName());              

        System.out.println("E = " + e.getName());              
        System.out.println("F = " + f.getName());              
        System.out.println("G = " + g.getName());              
        System.out.println("H = " + h.getName());
    }
}

The test program output looks as follows:

A = HondaAccord
B = HondaAccord, SunRoof
C = HondaAccord, TintedWindows
D = HondaAccord, TintedWindows, SunRoof
E = TeslaRoadster
F = TeslaRoadster, SunRoof
G = TeslaRoadster, TintedWindows
H = TeslaRoadster, SunRoof, TintedWindows

From this bare-bones example, you can see how we can programmatically alter an object's behavior at runtime- without affecting the original object's behavior.