Black Friday Black Friday
Black Friday ultimate sale! Get up to 80 % extra free points! More information here

Decorator

Software design Design patterns GOF Structural Patterns Decorator

Sometimes we need to add additional functionality to a class or group of classes, but inheritance is not a good solution. Such situations can occur, for example, when using third-party libraries that use sealed classes. We also don't need to know the internal implementation of the class being inherited. The last example can be a situation where simply don't want to use inheritance - inheritance is considered to be a very tight bonding of classes. That's not always the behavior we need. Let's recall the rule of inheritance - it must be possible to use the derived class in all cases where the base class is used. If we violate this rule by inappropriate inheritance, we violate the OOP principles. Using Decorator is a suitable solution in such situations.

Practical use

It's quite possible that you've already seen the Decorator design pattern, but you didn't know about it. Traditionally, it's used in GUI applications - such as scrollbars at the edges of the screen. We can scroll through anything - an image, text or a website. If we were to implement this functionality to each control element, we'd practically violate the principle of object-oriented programming because there would be the exactly same code at difference places of our application. Instead, we create a decorator that wraps the control element and renders the scrollbars and assigns the functionality to them. We delegate the rest of the actions to the original class. Likewise, we could add a frame to any element. Decorator only takes care of rendering the frame and delegates the rest of the functionality to the original class.

The second case can be working with input and output. When writing to a file, we might want to use a caching system. When sending data over the Internet, we might need to split the text into separate segments. When storing data into the database, we might need to create a connection. It wouldn't be optimal to implement each of these functionalities into a separate class. Again, the Decorator pattern can help us. It'll take responsibility for a specific task (connection to a database, caching), and the original class will do the rest.

Implementation

As I've already mentioned, the decorator delegates a lot of functionality to the original class. That means, firstly, it has to get its instance somewhere (most often from the constructor), and secondly, it must keep the same interface as the original class. This allows to extend the functionality without changing anything in the program. This idea is one of the key principles of the object-oriented programming and says we should program against an interface, not the implementation. This approach also requires the original class (the one we're decorating) to implement an interface. As an example, we'll show how the implementation of rendering a frame around an image and text in a GUI application would look like.

A UML diagram of a Decorator pattern implementation

The Frame class is a decorator for the IDrawable Interface. It takes the object in the constructor and wraps its Draw() method. The place parameter is only information about where the image or text should be rendered. Note that the Frame itself implements the IDrawable interface.

Imagine we have another decorator that adds scrollbars to the screen edge. With the current design, there is nothing stopping us from creating a frame with scrollbars which will move text or an image. It's just about the right nesting. In the following code, we implement this situation in practice. First, we create the interface and basic classes:

interface IDrawable
{
    void Draw(Place where);
    void Click();
}

class Image : IDrawable
{
    private byte[] ImageSource;

    public Image(byte[] Source)
    {
        this.ImageSource = Source;
    }

    public void Draw(Place where)
    {
        // drawing the image
    }

    public void Click()
    {
        ImageZoom();
    }
}

class Text : IDrawable
{
    private string TextToDraw;

    public Text(string Text)
    {
        this.TextToDraw = Text;
    }

    public void Draw(Place where)
    {
        // rendering the text
    }

    public void Click()
    {
        TextSelect();
    }
}

The Draw() method is called when the element is being displayed on the screen. The Click() method is called after clicking on the element with the mouse. Now let's have a look at our decorators:

class Frame : IDrawable
{
    private IDrawable WrappedObject;

    public Frame(IDrawable Object)
    {
        this.WrappedObject = Object;
    }

    public void Draw(Place where)
    {
        // drawing the frame
        where.RemoveSpace(); // subtracts the space the frame takes
        this.WrappedObject.Draw(where);
    }

    public void Click()
    {
        this.WrappedObject.Click();
    }
}

class Scrollbar : IDrawable
{
    private IDrawable WrappedObject;

    public Scrollbar(IDrawable Object)
    {
        this.WrappedObject = Object;
    }

    public void Draw(Place where)
    {
        // drawing the scrollbar
        where.RemoveSpaceForSlider();
        this.WrappedObject.Draw(where);
    }

    public void Click()
    {
        if (SliderClicked)
            this.MoveObject();
        else
            this.WrappedObject.Click();
    }
}

Note that the decorator always calls the original object's method. This is not the rule, but it often works like that. Next, you can notice the Click() method in the Frame class. Frame serves only for rendering and isn't intended for the interaction, so it just passes the control flow to the original object

Now let's have a look at how the decorator would be used in the code:

IDrawable ImageWithFrame = new Frame(new Image(data));
IDrawable TextWithScrollableFrame = new Scrollbar(new Frame(new Text("Text to render")));
IDrawable TextWithScrollbar = new Scrollbar(new Text("Text to render"));

Conclusion

You've certainly noticed the main disadvantage of this design pattern - the decorator has to reimplement all the methods. In the end, however, it does only one activity - it passes control to the inner object. For a simple functionality, this is a lot of extra programming. Therefore, for the typical application, we create an abstract class first that only delegates interface calls to the inner object. The decorator then inherits from this abstract class and overrides the desired methods only:

abstract class Decorator : IDrawable
{
    protected IDrawable WrappedObject;

    public Decorator(IDrawable Object)
    {
        this.WrappedObject = Object;
    }

    public void Draw(Place where)
    {
        WrappedObject.Draw(where);
    }

    public void Click()
    {
        WrappedObject.Click();
    }
}

class Frame : Decorator
{
    public void Draw(Place where)
    {
        // drawing the frame
        where.RemoveSpace(); // subtracts the space the frame takes
        WrappedObject.Draw(where);
    }
}

We can notice a certain similarity to the Adapter design pattern. The main difference is that Adapter changes the interface of the original class, preventing the program from using the original class (for example, because it has an incompatible interface). On the other hand, Decorator keeps the original interface and extends it further.

There's also a question of why not to favor inheritance and derive a new class that will have additional functionality. A good programmer should sense when it's appropriate to use inheritance and when other language constructs. Inheritance is one of the closest possible relationships between the base and derived class. If we start with inheritance and find out it doesn't fit our use case, it would be hard to rewrite the program.


 

 

Article has been written for you by David Capka
Avatar
Do you like this article?
No one has rated this quite yet, be the first one!
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn College The author learned IT at the Unicorn College - a prestigious college providing education on IT and economics.
Thumbnail
Previous article
Proxy
Thumbnail
All articles in this section
GOF - Structural Patterns
Thumbnail
Next article
Flyweight
Activities (2)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!