Design patterns: Decorator

in #design-patterns6 years ago

decorator.jpg

Decorator is one of the most important design patterns, allowing the extension of the existing class, more in detail about the decorator in the article.

Description and implementation method

As the name of the pattern indicates, it is used to decorate, or add an existing class, a new behavior, but does not change the operation of the base class. So it meets the first two solid principles, the single responsibility principle and the open-closed principle.

Let’s see how it looks in practice.

Structure

The pattern is quite simple to understand, as it was said in the introduction, it is used to add new behaviors to the existing class, as shown in the UML diagram below:

Decorator__1.png

It can be seen that the next classes are extended with new functionalities. It is done so that the classes are flexible, easy to modify and readable. About the details of the implementation further in the article.

Example

Example with cars

Suppose we have the mercedes, fiat and golf classes and the abstract class Car, which tells what methods these classes will have.

public abstract class Car
{
    public abstract double CostCar();
 
    public abstract string TypeEngine();
 
    public abstract string ColorCar();
}

class Fiat : Car
{
    public override string ColorCar()
    {
        return "white";
    }
 
    public override double CostCar()
    {
        return 10000.32;
    }
 
    public override string TypeEngine()
    {
        return "engine-v3";
    }
}

class Golf:Car
{
    public override string ColorCar()
    {
        return "black";
    }
 
    public override double CostCar()
    {
        return 23450.32;
    }
 
    public override string TypeEngine()
    {
       return "engine-v7";
    }
}

class Mercedes:Car
{
    public override string ColorCar()
    {
       return "Green";
    }
 
    public override double CostCar()
    {
       return 36750.52;
    }
 
    public override string TypeEngine()
    {
        return "engine-v3e5";
    }
}

I do not to know on cars at all, so I took prices, colors and engine names from outer space 🙂

But when it comes to the principle, everything seems fine, each car has its own engine, color and price.

However, not everything is fine … let’s add the MercedesWithRadioAndAC class, which is a car with radio and air conditioning and the MercedesWithRadio class:

class MercedesWithRadioAndAC : Car
{
    public override string ColorCar()
    {
        return "gray";
    }
 
    public override double CostCar()
    {
        return 45350.65;
    }
 
    public override string TypeEngine()
    {
        return "engine-v3e5sd";
    }
}
 
class MercedesWithRadio:Car
{
    public override string ColorCar()
    {
        return "white";
    }
 
    public override double CostCar()
    {
        return 41350.65;
    }
 
    public override string TypeEngine()
    {
        return "engine-v3e5s";
    }
}

You can see that here we do something wrong, if the price of the radio will change, we would have to change the price in all classes, if there were, for example, 10 or more, it would be a massacre. It’s better to make one particular radio-related class and then we would change the price in only one place.

First, however, before we create a radio decorator, first we have to create a car decorator, which will be the basis for creating more specific decorators.

It looks like this:

public abstract class CarDecorator:Car
{
    protected Car _car;
 
    public CarDecorator(Car car)
    {
       _car = car;
    }
 
    public override string ColorCar()
    {
        return _car.ColorCar();
    }
 
    public override double CostCar()
    {
        return _car.CostCar();
    }
 
    public override string TypeEngine()
    {
        return _car.TypeEngine();
    }
}

We already have a stand to create more specific decorators, so let’s create a radio decorator class:

public class RadioDecorator:CarDecorator
{
    public RadioDecorator(Car car)
    :base(car){}
 
    public override string ColorCar()
    {
        return base.ColorCar();
    }
 
    public override double CostCar()
    {
        return base.CostCar()+1232.32;
    }
 
    public override string TypeEngine()
    {
        return base.TypeEngine();
    }
}

So the end result is that we call methods from previous inheriting classes and increase the price because we added the radio:

static void Main(string[] args)
{
     Car mercedes = new Mercedes();
 
     mercedes = new RadioDecorator(mercedes);
 
     Console.WriteLine("The cost of a car with radio: "+ mercedes.CostCar());
 
     Console.ReadKey();
}

As you can see, the use of the decorator is simple, we create a Mercedes object and transmit the Mercedes object to the radio decorator. The result looks like this:

decoratorone.png

Continuation of the previous example (extension)

Let’s do another example, let’s make a car with air conditioning and a newer engine model, let it be fiat:

public class ACAndNewestEngineModelDecorator:CarDecorator
{
    public ACAndNewestEngineModelDecorator(Car car)
    :base(car){ }
 
    public override string ColorCar()
    {
        return base.ColorCar();
    }
 
    public override double CostCar()
    {
        return base.CostCar() + 12332.32;
    }
 
    public override string TypeEngine()
    {
        return base.TypeEngine()+"a7c";
    }
}

And call in the Main function:

static void Main(string[] args)
{
     Car fiat = new Fiat();
 
     fiat = new ACAndNewestEngineModelDecorator(fiat);
 
     Console.WriteLine("The cost of a car with air conditioning and a newer engine: "+ fiat.CostCar());
     Console.WriteLine("The name of the newer engine model: " + fiat.TypeEngine());
 
     Console.ReadKey();
}

The result looks like this:

decoratortwo.png

As you can see in the Main function, we are injecting a dependence to the constructor, if we had more dependencies, we could use the IoC container, it’s such a curiosity 🙂

Relations with other design patterns

  1. The adapter provides a different interface for the object. Proxy provides the same interface. The decorator provides an extended interface.
  2. The decorator can be seen as a composite with only one component. However, the decorator adds additional responsibilities – it is not designed to aggregate objects.
  3. The decorator has been designed to allow adding responsibilities to objects without a subclass. The composite focuses not on embellishment, but on the representation. These patterns often complement each other. Therefore, the Composite and Decorator are often used together.
  4. The decorator and proxy have different goals, but similar structures. Both describe how to provide an intermediate level to another object, and implementations retain a reference to the object to which they send requests.

Summary

That’s all about Decorator🙂.

Link to github with the whole code from this article: https://github.com/Slaw145/DecoratorTutorial

This content also you can find on my blog http://devman.pl/programtech/design-patterns-decorator/

If you recognise it as useful, share it with others so that others can also use it.

Leave upvote and follow and wait for next articles :) .

In the next article, we will talk about the Facade pattern.

And NECESSERILY join the DevmanCommunity community on fb, part of the community is in one place 🙂

– site on fb: Devman.pl-Sławomir Kowalski

– group on fb: DevmanCommunity

Ask, comment underneath at the end of the post, share it, rate it, whatever you want🙂.

Coin Marketplace

STEEM 0.20
TRX 0.14
JST 0.029
BTC 67494.12
ETH 3222.64
USDT 1.00
SBD 2.65