Real-world Application of Structural Patterns in JavaScript

Real-world Application of Structural Patterns

We are progressing in our understanding of Structural Patterns. In this lesson, we’ll see how to apply them in a practical example by creating a GUI library. So far, we’ve explored the Adapter, Composite, and Decorator Patterns independently. Now, we’ll integrate these patterns within a GUI library context to form a cohesive project. Note that these patterns are abstract and can be applied in various other scenarios beyond GUI libraries.

Adapter Pattern Recap

Let's quickly revisit the Adapter Pattern. This pattern allows two incompatible interfaces to work together. We accomplish this by creating an adapter class that converts one interface to another. In the context of our GUI library, consider the following classes:

class WinButton {
    render() {
        console.log("Rendering a button in a Windows style.");
    }
}

class MacButton {
    display() {
        console.log("Rendering a button in a Mac style.");
    }
}

Our WinButton class has a render method, while the MacButton class has a display method. To adapt MacButton to work within systems expecting a WinButton interface, we apply the Adapter Pattern by creating an adapter class:

class ButtonAdapter extends WinButton {
    constructor(button) {
        super();
        this.button = button;
    }
    
    render() {
        this.button.display();
    }
}

Here, ButtonAdapter adapts MacButton to the WinButton interface, allowing it to be used interchangeably. This allows the MacButton instance to be treated like a WinButton.

Intermediate Adapter Steps

First, let's create the MacButton and wrap it with the ButtonAdapter:

const macButton = new MacButton();
const winButton = new ButtonAdapter(macButton);
winButton.render(); // Output: Rendering a button in a Mac style.

This code instantiates a MacButton and adapts it using ButtonAdapter, enabling it to render with a Windows style.

Composite Pattern Recap

The Composite Pattern helps us compose objects into tree structures to represent part-whole hierarchies. This allows clients to treat individual objects and compositions of objects uniformly. For our GUI library, we use the Composite Pattern to manage components:

class Component {
    render() {
        throw new Error("Method 'render()' must be implemented.");
    }
}

Let's create a Container class that will act as a composite class containing other components:

class Container extends Component {
    constructor() {
        super();
        this.components = [];
    }

    add(component) {
        this.components.push(component);
    }

    remove(component) {
        this.components = this.components.filter(comp => comp !== component);
    }
    
    render() {
        this.components.forEach(component => component.render());
    }
}

We also need a Button class that will be a concrete component:

class Button extends Component {
    render() {
        console.log("Rendering a button.");
    }
}

The Container can hold and manage multiple components, such as buttons, efficiently, implementing the Composite Pattern.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal