Exploring Design Patterns: Factory, Builder, and Singleton Patterns

Exploring Design Patterns: Factory, Builder, and Singleton Patterns

ยท

5 min read

As a developer, you have probably encountered situations where you need to solve complex problems while also keeping your code organized, easy to maintain, and reusable. This is where design patterns come in - they are proven solutions to common problems that arise in software development. In this blog post, we will explore three design patterns that every developer should know, grouped into three categories: creational, structural, and behavioral.

we will be using a burger ๐Ÿ” as an example to help illustrate the concepts behind each of these design patterns. By the end, you will have a better understanding of how these patterns can be applied in real-world scenarios, and how they can help you write cleaner, more efficient code. So, let's dive in and explore the world of design patterns!

  • Factory Pattern:

The factory pattern is a creational pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created. This pattern is useful when you need to create objects that share common characteristics, but also have different implementations based on specific requirements.

In this pattern, we create a factory class that has the responsibility of creating objects. This allows us to encapsulate the creation of objects and decouple it from the rest of the code. In our burger restaurant example, we could create a BurgerFactory class that creates different types of burgers.

example:

class BurgerFactory {
public:
    static Burger createBurger(std::string type) {
        if (type == "cheeseburger") {
            return Cheeseburger();
        } else if (type == "baconburger") {
            return Baconburger();
        } else if (type == "veggieburger") {
            return Veggieburger();
        } else {
            throw std::invalid_argument("Invalid burger type.");
        }
    }
};

In this example, we have a static method called createBurger that takes a string parameter type. Based on the value of type, the factory method creates and returns a corresponding instance of a burger. If an invalid burger type is specified, the method throws an exception.

Let's say a customer orders a baconburger. We can use the BurgerFactory class to create a Baconburger object like this:

Burger baconburger = BurgerFactory::createBurger("baconburger");

By using the factory method, we have decoupled the creation of the Baconburger object from the rest of the code. If we ever need to change how the Baconburger object is created, we can do so in the BurgerFactory class without affecting the rest of the code.

That's the beauty of the Factory Pattern - it provides a way to create objects in a flexible and decoupled way.

  • Builder Pattern:

The builder pattern is another creational pattern that allows you to create complex objects step-by-step by separating the construction of a complex object from its representation. This pattern is useful when you need to create objects that have many optional parameters or when the object itself is complex and hard to construct in one step.

example:

class Burger {
public:
    void addPatty() {
        // add patty to the burger
    }

    void addCheese() {
        // add cheese to the burger
    }

    void addTomato() {
        // add tomato to the burger
    }

    void addLettuce() {
        // add lettuce to the burger
    }
};

class BurgerBuilder {
private:
    Burger* burger;

public:
    BurgerBuilder() {
        burger = new Burger();
    }

    Burger* getResult() {
        return burger;
    }

    void addPatty() {
        burger->addPatty();
    }

    void addCheese() {
        burger->addCheese();
    }

    void addTomato() {
        burger->addTomato();
    }

    void addLettuce() {
        burger->addLettuce();
    }
};

int main() {
    BurgerBuilder* builder = new BurgerBuilder();
    builder->addPatty();
    builder->addCheese();
    builder->addTomato();
    builder->addLettuce();
    Burger* myBurger = builder->getResult();
    delete builder;
    return 0;
}

In the Burger example, the BurgerBuilder class is used to construct a Burger object step by step. The BurgerBuilder class has methods to add different ingredients to the burger, and a getResult() method that returns the finished burger. In the main function, a BurgerBuilder object is created and the addPatty(), addCheese(), addTomato(), and addLettuce() methods are called to add the desired ingredients. Finally, the getResult() method is called to get the finished Burger object.

  • Singleton Pattern:

The singleton pattern is a creational pattern that ensures that a class has only one instance and provides a global point of access to it. This pattern is useful when you need to ensure that only one instance of a class exists and that it can be easily accessed throughout the application.

example:

class Burger {
private:
    static Burger* instance; // static variable to hold the instance
    Burger() {} // private constructor

public:
    static Burger* getInstance() {
        if (instance == nullptr) {
            instance = new Burger();
        }
        return instance;
    }

    void addPatty() {
        // add patty to the burger
    }

    void addCheese() {
        // add cheese to the burger
    }
};

Burger* Burger::instance = nullptr; // initialize static variable

int main() {
    Burger* myBurger = Burger::getInstance(); // get the singleton instance
    myBurger->addPatty(); // add a patty to the burger
    myBurger->addCheese(); // add cheese to the burger
    return 0;
}

In the Burger example, the Burger class is designed using the Singleton pattern to ensure that only one instance of the Burger class can exist at a time. The Burger class has a private constructor to prevent the creation of objects from outside the class, and a static method getInstance() that returns a pointer to the singleton instance. When getInstance() is called for the first time, it creates a new Burger object and sets the static instance variable to point to it. Subsequent calls to getInstance() simply return the existing instance. The addPatty() and addCheese() methods are used to add a patty and cheese to the burger respectively.

Conclusion-

Design patterns are a powerful tool that can help developers write code that is more organized, maintainable, and reusable. The three patterns we discussed in this blog post - Factory, Builder, and Singleton - fall under the creational category and are used to create objects flexibly and efficiently. By using these patterns, developers can write code that is more scalable, easier to maintain, and less prone to errors.

ย