• Backend Weekly
  • Posts
  • Part 4: Adapter and Composite Design Patterns (GoF)

Part 4: Adapter and Composite Design Patterns (GoF)

We will explore the Adapter and Composite Design Patterns under the Structural Design Patterns.

In partnership with

Hello “👋

Welcome to another week, another opportunity to become a Great Backend Engineer.

Today’s issue is brought to you by Masteringbackend → A great resource for backend engineers. We offer next-level backend engineering training and exclusive resources.

Before we get started, I have a few announcements:

We recently launched the first modules of three of our courses with a 63% discount for you. Use the coupon code PRESALE at checkout.

Here are what to expect in each course.

  • 10+ in-depth modules

  • 50+ in-depth chapters

  • 160+ high-quality lessons

  • 60+ hours of video training content

Note that the discounted prices increase monthly as we add new modules. Also, you can pick up a single course from our collection on the MB Platform.

Check this out:

All your news. None of the bias.

Be the smartest person in the room by reading 1440! Dive into 1440, where 3.5 million readers find their daily, fact-based news fix. We navigate through 100+ sources to deliver a comprehensive roundup from every corner of the internet – politics, global events, business, and culture, all in a quick, 5-minute newsletter. It's completely free and devoid of bias or political influence, ensuring you get the facts straight.

Now, back to the business of today.

In this series, I will explore Design Patterns, their types, the GoF design patterns, drawbacks, and benefits for backend engineers.

This comes from my new Vue.js book on “Vue Design Patterns”. However, I’m only transferring the knowledge to backend engineers in this series.

Today, we will explore the Adapter and Composite Design Patterns under the Structural Design Patterns.

Let’s get started quickly.

What is an Adapter Pattern?

The adapter pattern converts the interface of a class into another interface that a client expects. It allows classes to work together that couldn’t otherwise because of incompatible interfaces.

The Adapter Pattern is useful because it allows incompatible interfaces to work together by providing a way to "adapt" one interface to another.

It acts as a bridge between two components that otherwise wouldn’t be able to interact. This pattern is especially beneficial when integrating systems or classes that have different interfaces or when working with legacy code that you cannot modify directly.

It is used to create a new system from a legacy system as seen in the code snippet below:

class OldAPI {
    request() {
        return "Old API response";
    }
}

class NewAPI {
    constructor() {
        this.oldAPI = new OldAPI();
    }

    request() {
        return this.oldAPI.request();
    }
}

const api = new NewAPI();
console.log(api.request()); // "Old API response"

Here’s a real-world example:

Let’s assume your Payment system supports an old payment provider in your legacy system. However, you created a new system but you still want to support your old payment system but also create an adapter to allow you to change the payment system anytime.

Here’s how you can achieve it with Adapter Pattern:

// Third-party payment gateway (incompatible with our system)
class ThirdPartyPaymentGateway {
    processPayment(amount) {
        console.log(`Payment of ${amount} processed by third-party gateway.`);
    }
}

// Our system expects a "makePayment" method
class PaymentProcessor {
    constructor(paymentAdapter) {
        this.paymentAdapter = paymentAdapter;
    }

    makePayment(amount) {
        this.paymentAdapter.pay(amount);
    }
}

// Adapter to make the third-party gateway compatible
class PaymentAdapter {
    constructor(thirdPartyGateway) {
        this.thirdPartyGateway = thirdPartyGateway;
    }

    pay(amount) {
        this.thirdPartyGateway.processPayment(amount); // Adapts the method
    }
}

// Client code
const thirdPartyGateway = new ThirdPartyPaymentGateway();
const paymentAdapter = new PaymentAdapter(thirdPartyGateway);
const paymentProcessor = new PaymentProcessor(paymentAdapter);

paymentProcessor.makePayment(100);

From the code snippet above, you can easily connect or disconnect the ThirdPartyPaymentGateway using the PaymentAdapter. Therefore, making your application adaptable to other payment systems in the future.

Here is how the builder pattern is useful:

  • Solves interface incompatibility: It allows two incompatible interfaces to communicate with each other, bridging the gap between different systems.

  • Promotes code reusability: You can reuse existing or legacy systems without modifying their code.

  • Provides flexibility: It allows you to design your system independently of specific dependencies and easily extend it to support new integrations.

  • Facilitates legacy system integration: Legacy code can be integrated into modern systems without altering the original code.

  • Follows Open/Closed Principle: The Adapter Pattern makes your system more maintainable by allowing extensions through adapters rather than modifying existing code.

The Adapter Pattern is particularly useful in scenarios where you need to integrate different systems, work with legacy code, or handle various third-party APIs that don’t conform to the interface expected by your application. It helps create flexible, reusable, and maintainable systems while ensuring compatibility between different components.

What are Composite Patterns?

The composite pattern is used to compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.

It simplifies the management of hierarchical, tree-like structures, such as menus, file systems, or organizational charts, by allowing you to compose objects into tree structures and work with them as if they were individual objects.

This pattern is especially useful when dealing with recursive structures, where a group of objects (composites) can contain other objects or groups, and both individual objects and composites can be treated in the same way.

Here’s a code snippet to demonstrate:

First, we create a FileSystemComponent file and add the add, remove, and display methods to add, remove, and display all components respectively.

class FileSystemComponent {
    add(component) {
        throw new Error("Method not implemented.");
    }
    
    remove(component) {
        throw new Error("Method not implemented.");
    }
    
    display(indent = '') {
        throw new Error("Method not implemented.");
    }
}

Next, we created a File class that extends FileSystemComponent and overrides the display methods.

class File extends FileSystemComponent {
    constructor(name) {
        super();
        this.name = name;
    }

    display(indent = '') {
        console.log(`${indent}File: ${this.name}`);
    }
}

Next, we created a Directory folder that will allow us to add, remove, and display the components (Files) within it.

class Directory extends FileSystemComponent {
    constructor(name) {
        super();
        this.name = name;
        this.children = [];
    }

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

    remove(component) {
        this.children = this.children.filter(child => child !== component);
    }

    display(indent = '') {
        console.log(`${indent}Directory: ${this.name}`);
        this.children.forEach(child => child.display(indent + '  '));
    }
}

Lastly, this is how it will be used inside the client code:

// Client code
const rootDir = new Directory('root');
const file1 = new File('file1.txt');
const file2 = new File('file2.txt');

const subDir = new Directory('subdir');
const file3 = new File('file3.txt');
subDir.add(file3);

rootDir.add(file1);
rootDir.add(file2);
rootDir.add(subDir);

rootDir.display();

Here, we create different files and add them to the directory that we have created. As you can see from the code snippets above, we can treat Files and Directories as a whole instead of separate and individual parts.

Here is how the composite pattern is useful:

The Composite Pattern is particularly beneficial in scenarios where you need to manage tree-like or hierarchical structures. Its main advantages include:

  • Handling hierarchical structures: It simplifies working with recursive structures like file systems, UI components, and organizational charts.

  • Uniformity: It allows you to treat individual objects and compositions (groups of objects) consistently, reducing code complexity.

  • Extensibility: New components (either individual or composite) can be added without modifying existing code.

  • Simplified client code: Client code interacts with the structure uniformly, avoiding the need for complex type-checking logic.

  • Supports recursive composition: Complex hierarchies, where composites contain other composites, can be easily managed.

By using the Composite Pattern, you can build flexible, scalable, and maintainable systems that handle complex object structures with minimal effort.

That will be all for today. I like to keep this newsletter short.

Today, I discussed the Adapter and Composite Patterns, you learned how to implement them, their similarities, and when to use which.

Next week, I will start exploring the Proxy and Flyweight Design Patterns.

Don’t miss it. Share with a friend

Did you learn any new things from this newsletter this week? Please reply to this email and let me know. Feedback like this encourages me to keep going.

See you on Next Week.

Remember to start learning backend engineering from our courses:

Top 5 Remote Backend Jobs this week

Here are the top 5 Backend Jobs you can apply to now.

👨‍💻 Stripe
✍️ Backend / API Engineer, Local Payment Methods
đź“ŤRemote, Dublin, Ireland
đź’° Click on Apply for salary details
Click here to Apply for this role.

👨‍💻 LaunchDarkly
✍️ Backend Engineer - AI
đź“ŤRemote, US
đź’° Click on Apply for salary details
Click here to Apply for this role.

👨‍💻 Pinterest
✍️ Backend Software Engineer
đź“ŤRemote, Worldwide
đź’° Click on Apply for salary details
Click here to Apply for this role.

👨‍💻Sportradar
✍️ Backend Engineer
đź“ŤRemote
đź’° Click on Apply for salary details
Click here to Apply for this role.

Want more Remote Backend Jobs? Visit GetBackendJobs.com

Backend Engineering Resources

Whenever you're ready

There are 4 ways I can help you become a great backend engineer:

1. The MB Platform: Join 1000+ backend engineers learning backend engineering on the MB platform. Build real-world backend projects, track your learnings and set schedules, learn from expert-vetted courses and roadmaps, and solve backend engineering tasks, exercises, and challenges.

2. ​The MB Academy:​ The “MB Academy” is a 6-month intensive Advanced Backend Engineering BootCamp to produce great backend engineers.

3. MB Video-Based Courses: Join 1000+ backend engineers who learn from our meticulously crafted courses designed to empower you with the knowledge and skills you need to excel in backend development.

4. GetBackendJobs: Access 1000+ tailored backend engineering jobs, manage and track all your job applications, create a job streak, and never miss applying. Lastly, you can hire backend engineers anywhere in the world.

LAST WORD đź‘‹ 

How am I doing?

I love hearing from readers, and I'm always looking for feedback. How am I doing with The Backend Weekly? Is there anything you'd like to see more or less of? Which aspects of the newsletter do you enjoy the most?

Hit reply and say hello - I'd love to hear from you!

Stay awesome,
Solomon

I moved my newsletter from Substack to Beehiiv, and it's been an amazing journey. Start yours here.

Reply

or to participate.