Need to derive the decorator from component in decorator design pattern

Viewed 78

I was going through design patterns tutorials and came across decorator patterns. I got a idea of how and when the decorator pattern is used however, I am bit confused on why the decorator needs to be derived from the component.

The example I saw was something as below :

//Component classes
public abstract class Car
{
   public abstract int GetPrice();
   //Other properties and methods
}

public class ActualCar : Car
{
   public int GetPrice(){return 1000;}
}

//Decorators classes
public class CarDecorator : Car //No idea why this is necessary
{
   protected Car car;
   public CarDecorator(Car car)
   {
      this.car = car;
   }

   public override int GetPrice() => this.car.GetPrice();
}

public class LeatherSeats : CarDecorator
{
   public LeatherSeats(Car car) : base(car){}
   public override int GetPrice() => this.car.GetPrice() + 200;
}

public class AlloyWheels : CarDecorator
{
   public AlloyWheels(Car car) : base(car) {}
   public override int GetPrice() => this.car.GetPrice() + 150;
}

Now when using the component along with its decorators we use it as :

Car newCar = new ActualCar();
int price = new AlloyWheels(new LeatherSeats(newCar)).GetPrice();

Now I thought it was weird that the CarDecorator was inherited from Car as no matter how you look at it doesn't follow the is-a type of relationship. So I looked at few more examples and realized that it is how the decorator pattern is designed as.

I don't want to question the reason why the decorator pattern was designed this way, but just want to know what will be the cons of not having decorator pattern be derived from component it wraps.

2 Answers

Your CarDecorator which is a base decorator should inherit Car interface (here it's an abstract class) to ensure that CarDecorator has implemented (should override) GetPrice method.

If CarDecorator does not inherit from Car, then you can't do like this:

Car newCar = new ActualCar();
int price = new CarDecorator(newCar).GetPrice();

Car serves here pretty much like an interface, or a contract that explicitly tells that all concrete decorators should implement GetPrice method.

The purpose of the Decorator pattern is to provide a way to extend a class without subclassing.

"Attach additional responsibilities to an object dynamically keeping the same interface. Decorators provide a flexible alternative to subclassing for extending functionality." (Gang of Four)

This means for the client the decorator type must behave exactly like (mimic) the decorated type. Even more, the decorator type must be replaceable to the decorated type.

To make types replaceable they must be of the same base type:

interface IVehicle {}

class Car : IVehicle {}

class Bus : IVehicle {}

Now every IVehicle is replacable:

IVehicle car = new Car();
IVehicle bus = new Bus();

IVehicle vehicle = car;
vehicle = bus;

To allow the decorator type to be replaceable with the decorated type both must inherit the same base type. This means the decorator will actually extend the decorated base type:

// Common base class. Will be extended by a decorator.
public interface ICar
{
  ISound Honk();

  //Other properties and methods
}

public class Beagle : ICar 
{
  // Implementation of ICar
  public ISound Honk() => new BeagleHornSound(); // BeagleHornSound implements ISound
}

// Abstract decorator base class.
// Since the base class implements ICar, every CarDecorator "is-a" ICar.
// This makes CarDecorator and every ICar replaceable and allows the CarDecorator to mimic the decorated/wrapped ICar. 
// Therefore every feature of this class or subclass is actually extending every ICar.
public abstract class CarDecorator : ICar
{
  private ICar Car { get; set; }
  private CarDecorator(ICar car)
  {
    this.Car = car;
  }

  // Implementation of ICar
  public ISound Honk() => this.Car.Honk();
}

// Concrete decorator class
public abstract class Lowrider : CarDecorator
{
  private Hydraulics Hydraulics { get; set; }
  
  public CarDecorator(ICar car) : base (car)
  {
    this.Hydraulics = new Hydraulics();
  }

  public void EnableHydraulic() => LetCarHop();
}

Usage

// Extends the ICar by wrapping it into a decorator
public CarDecorator PimpCar(ICar car) 
{
  return new Lowrider(car);  
}

ICar beagle = new Beagle(); // Beagle implements ICar

CarDecorator lowrider = PimpCar(beagle); // Returns a Lowrider which extends CarDecorator

// Since the decorator also implements ICar, every CarDecorator is replaceable with every other ICar
ICar pimpedBeagle = lowrider; // Lowrider implements ICar

// Since the decorator "is-a" ICar, it can mimic every ICar
ISound hornSound = lowrider.Honk();

// Since the Lowrider is decorating/wrapping currently a Beagle, it actually mimics a Beagle
bool isSameType = lowrider.Honk() is BeagleHornSound; // true

Now you can see, that if the decorator won't implement the same type it is decorating, then you wouldn't have the decorated type extended, but created a new type.

You usually extend a type by inheriting from it and the add new features to the subclass. The Superclass (ancestor) is extended because the subclass (descendant) is still of the same type like its superclass (that's inheritance - same inheritance tree).
Decorator is just another strategy to achieve the same result, but without subclassing the type you wish to extend.

The decorator type (descendant) is actually the extended version of the decorated type (ancestor). It's like the decorator is a subclass (extension) of the decorated type.
To realize this in terms of type hierarchy, the decorator must be also of the same type like the superclass in order to be a true descendant. Basically the Decorator is faking inheritance using composition. Or the Decorator is a more flexible form of inheritance, as every type wrapped by a single decorator is actually extended without writing a new derived class of the wrapped type.

Related