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:

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.

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

publicabstractclassCar

{

publicabstractdoubleCostCar();

publicabstractstringTypeEngine();

publicabstractstringColorCar();

}

classFiat:Car

{

publicoverridestringColorCar()

{

return"white";

}

publicoverridedoubleCostCar()

{

return10000.32;

}

publicoverridestringTypeEngine()

{

return"engine-v3";

}

}

classGolf:Car

{

publicoverridestringColorCar()

{

return"black";

}

publicoverridedoubleCostCar()

{

return23450.32;

}

publicoverridestringTypeEngine()

{

return"engine-v7";

}

}

classMercedes:Car

{

publicoverridestringColorCar()

{

return"Green";

}

publicoverridedoubleCostCar()

{

return36750.52;

}

publicoverridestringTypeEngine()

{

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:

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

classMercedesWithRadioAndAC:Car

{

publicoverridestringColorCar()

{

return"gray";

}

publicoverridedoubleCostCar()

{

return45350.65;

}

publicoverridestringTypeEngine()

{

return"engine-v3e5sd";

}

}

classMercedesWithRadio:Car

{

publicoverridestringColorCar()

{

return"white";

}

publicoverridedoubleCostCar()

{

return41350.65;

}

publicoverridestringTypeEngine()

{

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:

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

publicabstractclassCarDecorator:Car

{

protectedCar _car;

publicCarDecorator(Car car)

{

_car=car;

}

publicoverridestringColorCar()

{

return_car.ColorCar();

}

publicoverridedoubleCostCar()

{

return_car.CostCar();

}

publicoverridestringTypeEngine()

{

return_car.TypeEngine();

}

}

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

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

publicclassRadioDecorator:CarDecorator

{

publicRadioDecorator(Car car)

:base(car){}

publicoverridestringColorCar()

{

returnbase.ColorCar();

}

publicoverridedoubleCostCar()

{

returnbase.CostCar()+1232.32;

}

publicoverridestringTypeEngine()

{

returnbase.TypeEngine();

}

}

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

C#

1

2

3

4

5

6

7

8

9

10

staticvoidMain(string[]args)

{

Car mercedes=newMercedes();

mercedes=newRadioDecorator(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:

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:

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

publicclassACAndNewestEngineModelDecorator:CarDecorator

{

publicACAndNewestEngineModelDecorator(Car car)

:base(car){}

publicoverridestringColorCar()

{

returnbase.ColorCar();

}

publicoverridedoubleCostCar()

{

returnbase.CostCar()+12332.32;

}

publicoverridestringTypeEngine()

{

returnbase.TypeEngine()+"a7c";

}

}

And call in the Main function:

C#

1

2

3

4

5

6

7

8

9

10

11

staticvoidMain(string[]args)

{

Car fiat=newFiat();

fiat=newACAndNewestEngineModelDecorator(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:

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

The adapter provides a different interface for the object. Proxy provides the same interface. The decorator provides an extended interface.

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.

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.

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.