In software engineering, Creational Design Patterns deal with object creation mechanisms, i.e. try to create objects in a manner suitable to the situation. The basic or ordinary form of object creation could result in design problems or added complexity to the design. In this article of the Creational Design Patterns, we’re going to take a look at the much-hated & commonly asked design pattern in a programming interview. That is Singleton Design Pattern in Modern C++ which criticizes for its extensibility & testability. I will also cover the Multiton Design Pattern which quite contrary to Singleton.

By the way, If you haven’t check out my other articles on Creational Design Patterns, then here is the list:

  1. Factory
  2. Builder
  3. Prototype
  4. Singleton

The code snippets you see throughout this series of articles are simplified not sophisticated. So you often see me not using keywords like override, final, public(while inheritance) just to make code compact & consumable(most of the time) in single standard screen size.

I also prefer struct instead of class just to save line by not writing “public:” sometimes and also miss virtual destructor, constructor, copy constructor, prefix std::, deleting dynamic memory, intentionally. I also consider myself a pragmatic person who wants to convey an idea in the simplest way possible rather than the standard way or using Jargons.

Note:

  • If you stumbled here directly, then I would suggest you go through What is design pattern? first, even if it is trivial. I believe it will encourage you to explore more on this topic.
  • All of this code you encounter in this series of articles are compiled using C++20(though I have used Modern C++ features up to C++17 in most cases). So if you don’t have access to the latest compiler you can use https://wandbox.org/ which has preinstalled boost library as well.

Intent

To ensure one & only one instance of a class exist at any point in time.

  • The Singleton Design Pattern ensures that a class has only one instance and provides a global point of access to that instance. This is useful when exactly one object need to coordinate actions across the system.
  • So, essentially, the Singleton Design Pattern is nothing more than specifying a lifetime.

Singleton Design Pattern C++ Example

  • The motivation for using a Singleton Design Pattern is fairly obvious. Some components in our system only need to have a single instance. For example, a database that loads up from its constructor into memory & then gives out information about its contents. Once it’s loaded up you don’t really want more than one instance of it because there is no point.
  • And you also want to prevent your clients/API-users from making any additional copies of that object. Following is a trivial example of the Singleton Design Pattern in C++.
/* country.txt 
Japan
1000000
India
2000000
America
123500
*/
class SingletonDatabase {
    std::map<std::string, int32_t>  m_country;

    SingletonDatabase() {
        std::ifstream ifs("country.txt");

        std::string city, population;
        while (getline(ifs, city)) {
            getline(ifs, population);
            m_country[city] = stoi(population);
        }
    }

public:
    SingletonDatabase(SingletonDatabase const &) = delete;
    SingletonDatabase &operator=(SingletonDatabase const &) = delete;

    static SingletonDatabase &get() {
        static SingletonDatabase db;
        return db;
    }

    int32_t get_population(const std::string &name) { return m_country[name]; }
};

int main() {
    SingletonDatabase::get().get_population("Japan");
    return EXIT_SUCCESS;
}

Some of the things to note here from the design perspective are:

  1. Private constructor
  2. Deleted copy constructor & copy assignment operator
  3. Static object creation & static method to access

The Problem of Testability With Singleton

  • So we have our Singleton database and let’s suppose that we decide to use this database to do some research and we actually made a new class called a SingletonRecordFinder which is going to find the total population from the collection of city names provided in the argument as follow.
struct SingletonRecordFinder {
    static int32_t total_population(const vector<string>&   countries) {
        int32_t result = 0;
        for (auto &country : countries)
            result += SingletonDatabase::get().get_population(country);
        return result;
    }
};
  • But let’s suppose that we decide that we want to test the SingletonRecordFinder and this is where all the problems show up.
vector<string> countries= {"Japan", "India"}; // Strongly tied to data base entries
TEST(1000000 + 2000000, SingletonRecordFinder::total_population(countries));
  • Unfortunately, because we are strongly tied to the real database and there is no way to substitute this database. I have to use the values taken from the actual file. And when later on these entries change, your test will start failing as you may have not updated the code. And this going to be a continuous problem.
  • Moreover, this is not going to be a unit-test rather it is integration test as we are not only testing our code but also a production database which is not good design.
  • Surely there is a better way of actually implementing this particular construct so that we can still use the singleton but if need we can supply an alternative to the singleton implementation with some dummy data of our own.

Singleton Design Pattern With Dependency Injection

  • The problem that we’re encountering in the testing of the SingletonRecordFinder is to do with the fact that we have a dependency upon essentially the details of how a database provides its data because we’re depending directly on the singleton database and the fact that it’s a singleton.
  • So why don’t we use a little bit of dependency injection on an interface or abstract class!
struct Database { // Dependency 
    virtual int32_t get_population(const string& country) = 0;
};

class SingletonDatabase : Database {
    map<string, int32_t>    m_countries;

    SingletonDatabase() {
        ifstream ifs("countries.txt");

        string city, population;
        while (getline(ifs, city)) {
            getline(ifs, population);
            m_countries[city] = stoi(population);
        }
    }

public:
    SingletonDatabase(SingletonDatabase const &) = delete;
    SingletonDatabase &operator=(SingletonDatabase const &) = delete;

    static SingletonDatabase &get() {
        static SingletonDatabase db;
        return db;
    }

    int32_t get_population(const string &country) { return m_countries[country]; }
};

class DummyDatabase : public Database {
    map<string, int32_t>    m_countries;
public:
    DummyDatabase() : m_countries{{"alpha", 1}, {"beta", 2}, {"gamma", 3}} {}
    int32_t get_population(const string &country) { return m_countries[country]; }
};

/* Testing class ------------------------------------------------------------ */
class ConfigurableRecordFinder {
    Database&       m_db;  // Dependency Injection
public:
    ConfigurableRecordFinder(Database &db) : m_db{db} {}
    int32_t total_population(const vector<string> &countries) {
        int32_t result = 0;
        for (auto &country : countries)
            result += m_db.get_population(country);
        return result;
    }
};
/* ------------------------------------------------------------------------- */

int main() {
    DummyDatabase db;
    ConfigurableRecordFinder rf(db);
    rf.total_population({"Japan", "India", "America"});
    return EXIT_SUCCESS;
}

#cpp #cplusplus #design-patterns #design-thinking #computer-science #programming #coding #code-quality

Design Patterns: Singleton Pattern in Modern C++
1.35 GEEK