Decorator Design Pattern: The Adornments of Software

[tr] Türkçe Oku

2023-06-15

Once upon a time, there was an object named “Component”. This object was used to perform a specific function. However, sometimes it was necessary to extend or modify this function. That’s where “Decorators” came into play.

Decorators are objects that “decorate” or extend the Component. A Decorator comes on top of a Component and extends or modifies its function. This is used to extend the functionality of the Component without changing it itself.

For example, consider a “TextComponent”. This component prints a text to the screen. However, sometimes we may want to make the text bold or italic. That’s where “BoldDecorator” or “ItalicDecorator” come into play. These decorators come on top of the TextComponent and make the text bold or italic.

The decorator pattern works in this way. With a component and a series of decorators that extend it, we can perform a wide variety of functions.

Here is a class diagram of the decorator pattern:

Decorator Pattern Class Diagram

In this diagram, you can see a “Component” and a series of “Decorators” that decorate or extend this component. The “ConcreteComponent” class extends the “Component” class and performs a specific function. The “Decorator” class also extends the “Component” class and contains a “Component” object. The “ConcreteDecoratorA” and “ConcreteDecoratorB” classes extend the “Decorator” class and perform a specific function.

First, I will give an example in C#:

// Component
public abstract class Component
{
    public abstract void Operation();
}

// ConcreteComponent
public class ConcreteComponent : Component
{
    public override void Operation()
    {
        // ...
    }
}

// Decorator
public abstract class Decorator : Component
{
    protected Component component;

    public Decorator(Component component)
    {
        this.component = component;
    }

    public override void Operation()
    {
        if (component != null)
        {
            component.Operation();
        }
    }
}

// ConcreteDecoratorA
public class ConcreteDecoratorA : Decorator
{
    public ConcreteDecoratorA(Component component) : base(component) { }

    public override void Operation()
    {
        base.Operation();
        // Additional behavior
    }
}

// ConcreteDecoratorB
public class ConcreteDecoratorB : Decorator
{
    public ConcreteDecoratorB(Component component) : base(component) { }

    public override void Operation()
    {
        base.Operation();
        // Additional behavior
    }
}

In this code, we created a “Component” and a series of “Decorators” that decorate or extend this component. The “ConcreteComponent” class extends the “Component” class and performs a specific function. The “Decorator” class also extends the “Component” class and contains a “Component” object. The “ConcreteDecoratorA” and “ConcreteDecoratorB” classes extend the “Decorator” class and perform a specific function.

Java:

// Component
public interface Component {
    void operation();
}

// ConcreteComponent
public class ConcreteComponent implements Component {
    public void operation() {
        // ...
    }
}

// Decorator
public abstract class Decorator implements Component {
    protected Component component;

    public Decorator(Component component) {
        this.component = component;
    }

    public void operation() {
        if (component != null) {
            component.operation();
        }
    }
}

// ConcreteDecoratorA
public class ConcreteDecoratorA extends Decorator {
    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    public void operation() {
        super.operation();
        // Additional behavior
    }
}

// ConcreteDecoratorB
public class ConcreteDecoratorB extends Decorator {
    public ConcreteDecoratorB(Component component) {


        super(component);
    }

    public void operation() {
        super.operation();
        // Additional behavior
    }
}

Go:

// Component
type Component interface {
    Operation() string
}

// ConcreteComponent
type ConcreteComponent struct{}

func (c *ConcreteComponent) Operation() string {
    return "ConcreteComponent"
}

// Decorator
type Decorator struct {
    component Component
}

func (d *Decorator) Operation() string {
    if d.component != null {
        return d.component.Operation()
    }
    return ""
}

// ConcreteDecoratorA
type ConcreteDecoratorA struct {
    Decorator
}

func (c *ConcreteDecoratorA) Operation() string {
    return "ConcreteDecoratorA(" + c.Decorator.Operation() + ")"
}

// ConcreteDecoratorB
type ConcreteDecoratorB struct {
    Decorator
}

func (c *ConcreteDecoratorB) Operation() string {
    return "ConcreteDecoratorB(" + c.Decorator.Operation() + ")"
}

Rust:

// Component
trait Component {
    fn operation(&self) -> String;
}

// ConcreteComponent
struct ConcreteComponent;

impl Component for ConcreteComponent {
    fn operation(&self) -> String {
        String::from("ConcreteComponent")
    }
}

// Decorator
struct Decorator<T: Component> {
    component: T,
}

impl<T: Component> Component for Decorator<T> {
    fn operation(&self) -> String {
        self.component.operation()
    }
}

// ConcreteDecoratorA
struct ConcreteDecoratorA<T: Component> {
    decorator: Decorator<T>,
}

impl<T: Component> Component for ConcreteDecoratorA<T> {
    fn operation(&self) -> String {
        let base_operation = self.decorator.operation();
        format!("ConcreteDecoratorA({})", base_operation)
    }
}

// ConcreteDecoratorB
struct ConcreteDecoratorB<T: Component> {
    decorator: Decorator<T>,
}

impl<T: Component> Component for ConcreteDecoratorB<T> {
    fn operation(&self) -> String {
        let base_operation = self.decorator.operation();
        format!("ConcreteDecoratorB({})", base_operation)
    }
}

These examples show how the decorator pattern can be implemented in different languages. Remember that each language has its own features and syntax, so examples for each language may be slightly different.



More posts like this

The Dance of Components: The Composite Design Pattern

2023-06-22 | #composite-pattern #design-patterns #structural-patterns

Once upon a time, there was a tree in a forest. This tree was a complex structure with leaves and branches. Each branch could have smaller branches and leaves on it. The tree worked as a whole, holding its branches and leaves together. This tree is an example of the Composite design pattern. The tree (Composite) contains two types of components: branches (also Composite) and leaves (Leaf). Both branches and leaves implement the same interface (Component) recognized by the tree.

Continue reading 


Gaining Flexibility by Building Bridges: Analysis and Implementations in Four Different Languages with the Bridge Design Pattern

2023-06-22 | #bridge-pattern #design-patterns #structural-patterns

Once upon a time, there were two kingdoms. Despite being very close to each other, a wide and deep river was located between them. This river made it difficult for the kingdoms to interact with each other. To solve this situation, both kingdoms decided to build a bridge. This bridge became an interface that facilitated communication between the two kingdoms. However, the bridge had different structures and features on each side.

Continue reading 


From the Language of Code to Fairy Tales: Uniting Different Worlds with the Adapter Pattern

2023-06-15 | #adapter-pattern #design-patterns #structural-patterns

Once upon a time, there were two friends from two different worlds: the Electric Vacuum Cleaner and the Electric Outlet. The Electric Vacuum Cleaner needed energy and wanted to get this energy from the Electric Outlet. However, there was a problem. The plug of the Electric Vacuum Cleaner did not fit the Electric Outlet. They were both produced in different standards and could not communicate directly with each other. In this case, a hero emerged: the Adapter.

Continue reading 