• Backend Weekly
  • Posts
  • Part 11: Visitor and Template Method Design Patterns (GoF)

Part 11: Visitor and Template Method Design Patterns (GoF)

We will explore the Visitor and Template method Design Patterns under the Behavioral 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 MBProjects→ MBProjects is a project-based learning platform designed specifically for backend engineers. It’s your opportunity to work on 100+ real-world backend projects that mirror actual problems you’d encounter in the industry.

Before we get started, I have a few announcements:

I have a special gift for you: You will love this one.

Learn AI in 5 Minutes a Day

AI Tool Report is one of the fastest-growing and most respected newsletters in the world, with over 550,000 readers from companies like OpenAI, Nvidia, Meta, Microsoft, and more.

Our research team spends hundreds of hours a week summarizing the latest news, and finding you the best opportunities to save time and earn more using AI.

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 Visitor and Template Method Design Patterns under the Behavioral Design Patterns.

Let’s get started quickly.

What is a Visitor Pattern?

The visitor’s pattern allows you to add further operations to objects without having to modify them.

The Visitor Pattern is useful because it allows you to define new operations on a set of objects without modifying their classes.

This pattern is particularly beneficial when you need to perform multiple unrelated operations across a collection of objects that have a shared interface or hierarchy.

Let’s take a look at this example, imagine a file system where different types of files (e.g., text files, image files) may need various operations like compression, encryption, or analysis. The Visitor Pattern allows you to create these operations as separate visitors, applying them to files without modifying the file classes.

// Element Interface
class File {
    accept(visitor) {
        throw new Error("Method 'accept()' must be implemented.");
    }
}

// Concrete Element: TextFile
class TextFile extends File {
    constructor(name) {
        super();
        this.name = name;
    }

    accept(visitor) {
        visitor.visitTextFile(this);
    }
}

// Concrete Element: ImageFile
class ImageFile extends File {
    constructor(name) {
        super();
        this.name = name;
    }

    accept(visitor) {
        visitor.visitImageFile(this);
    }
}

The code snippet above creates the file class and the child file objects such as the TextFile and ImageFile.

// Visitor Interface
class Visitor {
    visitTextFile(file) {
        throw new Error("Method 'visitTextFile()' must be implemented.");
    }

    visitImageFile(file) {
        throw new Error("Method 'visitImageFile()' must be implemented.");
    }
}

// Concrete Visitor: Compression
class CompressionVisitor extends Visitor {
    visitTextFile(file) {
        console.log(`Compressing text file: ${file.name}`);
    }

    visitImageFile(file) {
        console.log(`Compressing image file: ${file.name}`);
    }
}

// Concrete Visitor: Encryption
class EncryptionVisitor extends Visitor {
    visitTextFile(file) {
        console.log(`Encrypting text file: ${file.name}`);
    }

    visitImageFile(file) {
        console.log(`Encrypting image file: ${file.name}`);
    }
}

Next, we created the visitor’s pattern, here, we created the compression and encryption visitors that will be used on each type of file.

// Client code
const files = [new TextFile("document.txt"), new ImageFile("photo.jpg")];
const compressionVisitor = new CompressionVisitor();
const encryptionVisitor = new EncryptionVisitor();

files.forEach(file => file.accept(compressionVisitor));
// Output:
// Compressing text file: document.txt
// Compressing image file: photo.jpg

files.forEach(file => file.accept(encryptionVisitor));
// Output:
// Encrypting text file: document.txt
// Encrypting image file: photo.jpg

The code snippet shows how to use the visitor’s pattern in your codebase. Here, we used the different visitors we have created to perform actions with the file.

You can add new operations (CompressionVisitor, EncryptionVisitor) without modifying the TextFile or ImageFile classes, preserving the existing class design. Each visitor represents a specific operation, making the system more modular and allowing the operations to vary independently.

Why the Visitor Pattern is Useful

The Visitor Pattern is particularly beneficial when you need to perform multiple operations across a set of related classes. Its main advantages include:

  • Adds functionality without modifying existing classes: New operations can be added by creating new visitors, keeping the original classes unchanged.

  • Centralizes operations on related classes: Grouping operations in visitors makes the code more organized and modular.

  • Simplifies adding new operations: New visitors can be created independently, promoting extensibility.

  • Supports complex structures: The pattern is well-suited for composite structures or object hierarchies where you need to perform operations across different types of elements.

  • Promotes Open/Closed Principle: Classes remain closed for modification but open for extension, enhancing system stability and maintainability.

By using the Visitor Pattern, you can build systems that are more flexible, modular, and easier to extend, especially when working with complex object structures that require multiple, varied operations.

What is a Template Method Pattern?

The template method defines the skeleton of an algorithm, with one or more steps being deferred to subclasses. It lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.

This pattern is particularly beneficial when you have multiple variations of an algorithm with shared behavior.

By defining the invariant parts of the algorithm in a superclass and delegating customizable steps to subclasses, the Template Method Pattern promotes code reuse, consistency, and adherence to the Open/Closed Principle.

Let’s explore this example, and consider a data processing pipeline where multiple types of data need to be loaded, processed, and saved. Using the Template Method Pattern, the base class can define the skeleton of the pipeline while subclasses implement data-specific steps.

// Abstract Base Class
class DataProcessor {
    processData() {
        this.loadData();
        this.processData();
        this.saveData();
    }

    loadData() {
        throw new Error("Method 'loadData()' must be implemented.");
    }

    processData() {
        throw new Error("Method 'processData()' must be implemented.");
    }

    saveData() {
        console.log("Saving processed data...");
    }
}

The code snippet above creates the base class for data processing, within it, we have loadData, processData and saveData methods.

// Concrete Class: CSV Data Processor
class CSVDataProcessor extends DataProcessor {
    loadData() {
        console.log("Loading CSV data...");
    }

    processData() {
        console.log("Processing CSV data...");
    }
}

// Concrete Class: JSON Data Processor
class JSONDataProcessor extends DataProcessor {
    loadData() {
        console.log("Loading JSON data...");
    }

    processData() {
        console.log("Processing JSON data...");
    }
}

Next, we created the concrete types of files we wanted to process and extend the base data processor class.

// Client code
const csvProcessor = new CSVDataProcessor();
csvProcessor.processData();
// Output:
// Loading CSV data...
// Processing CSV data...
// Saving processed data...

const jsonProcessor = new JSONDataProcessor();
jsonProcessor.processData();
// Output:
// Loading JSON data...
// Processing JSON data...
// Saving processed data...

Lastly, in our client code, we instantiate the type of data classes and call the processData method from each instance to process the specific data.

The skeleton of the algorithm (load, process, save) is encapsulated in the base class (DataProcessor), ensuring consistency across different types of data processors. Subclasses (CSVDataProcessor, JSONDataProcessor) implement only the specific steps needed, avoiding code duplication.

Why the Template Method Pattern is Useful

The Template Method Pattern is particularly beneficial in scenarios where multiple variations of an algorithm share common steps but differ in specific details. Its main advantages include:

  • Encapsulates invariant parts of an algorithm: Keeps the algorithm’s structure consistent while allowing for customizable steps.

  • Promotes code reuse and reduces duplication: Shared behavior is centralized, minimizing code duplication across subclasses.

  • Allows subclasses to vary specific steps: Supports variations without modifying the base algorithm, adhering to the Open/Closed Principle.

  • Enhances readability and understandability: Clearly separates the high-level structure of the algorithm from the details, improving readability.

  • Simplifies maintenance and extension: Centralizing shared logic makes the codebase easier to update, extending shared behavior across all subclasses.

By using the Template Method Pattern, you can build systems that are more modular, maintainable, and consistent, especially when dealing with algorithms that have multiple variations with common steps.

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

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.

👨‍💻 Bitpanda
✍️ Backend Engineer, Typescript (Custody)
đź“ŤRemote
đź’° Click on Apply for salary details
Click here to Apply for this role.

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

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

👨‍💻 Zoox
✍️ Backend Engineer (f/m/d)
đź“Ť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.