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.