banner



The Perfect Designing Tool Ipo For C

Design Patterns: Singleton Pattern in Modern C++

Creational Design Patterns deal with object creation mechanisms, i.e. try to create objects in a manner suitable to the situation. 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. Following is a trivial example of the Singleton design pattern in C++. I believe it will encourage you to explore more on this topic. The code snippets you see throughout this series of articles are simplified not sophisticated.

image

Vishal Chovatiya Hacker Noon profile picture

@ IndianWestCoast

Vishal Chovatiya

Software Developer⌨, Fitness Freak🏋, Geek🤓, Hipster🕴, Blogger👨‍💻, Productivity Hacker⌚

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; }              

Due to Dependency Injection i.e. Database interface, our both following issues are resolved:

  1. We have done a proper unit test rather an integration test,
  2. Now our testing class is not directly tie-up to Singleton. So no need to change our unit-test over & over in accordance with a database change.

Multiton Design Pattern

  • Multiton is a variation to singleton but not directly linked to it. Remember that singleton prevents you to have additional instances while Multiton Design Pattern sets up kind of key-value pair along with the limitation for the number of instance creation.
                                  enum                                      class                    Importance                    {                  PRIMARY, SECONDARY, TERTIARY };                  template                  <typename                  T,                  typename                  Key =                  std::string> struct Multiton {                  static                  shared_ptr<T> get(const                  Key &key) {                  if                  (const                  auto                  it = m_instances.find(key); it != m_instances.end()) {                  // C++17                  return                  it->second;          }                  return                  m_instances[key] = make_shared<T>();     }                  private:                  static                  map<Key,                  shared_ptr<T>>  m_instances; };                  template                  <typename                  T,                  typename                  Key>                  map<Key,                  shared_ptr<T>>     Multiton<T, Key>::m_instances;                  // Just initialization of static data member                                      struct                    Printer                    {                  Printer() {                  cout                  <<                  "Total instances so far = "                  << ++InstCnt <<                  endl; }                  private:                  static                  int                  InstCnt; };                  int                  Printer::InstCnt =                  0;                                      int                    main                    ()                  {                  using                  mt = Multiton<Printer, Importance>;                  auto                  main = mt::get(Importance::PRIMARY);                  auto                  aux = mt::get(Importance::SECONDARY);                  auto                  aux2 = mt::get(Importance::SECONDARY);                  // Will not create additional instances                  return                  EXIT_SUCCESS; }              
  • So as you can see we have three printers i.e. primary, secondary & tertiary whose access & instantiation is controlled by Multiton. Rest of the code is self-explanatory I hope.

Benefits of Singleton Design Pattern

  1. The Singleton Design Pattern is quite helpful for application configurations as configurations may need to be accessible globally, and future expansions to the application configurations can be consolidated at single place.
  2. A second common use of this class is in updating old code to work in a new architecture. Since developers may have used globals liberally, moving them into a single class and making it a singleton, can be an intermediary step to bring the program inline to the stronger object-oriented structure.
  3. Singleton Design Pattern also enhance the maintainability as it provides a single point of access to a particular instance.

Summary by FAQs

What is so bad about the Singleton Design Pattern?

  • Singleton object holds the state for the lifetime of the application. Which is bad for testing since you can end up with a situation where tests need to be ordered which is a big no-no for unit tests. Why? Because each unit test should be independent of the other.
  • Singleton object causes code to be tightly coupled. This makes guessing the expected result under test scenarios rather difficult as we have seen above in database example. But you can overcome it by using Dependency Injection along with Singleton Design Pattern.
  • Imagine the situation where you have a concurrent application accessing Singleton object from every part of your application, It just mashes up things or slows it down if you use a mutex or any other synchronization primitives.

What is the correct way to implement Singleton Design Pattern?

The right way to implement Singleton is by dependency injection, So instead of directly depending on a singleton, you might want to consider it depending on an abstraction(e.g. an interface). I would also encourage you to use synchronization primitives(like a mutex, semaphores, etc) to control access.

When should you use the Singleton Design Pattern?

  • Usually, Singleton is used in hardware interface usage limitation. For example, Printers are limited in numbers, so in such case, a singleton or multiton design pattern is used to manage access.
  • Singleton Design Pattern is also widely employed in managing configuration or properties file to manage access.
  • We can use the cache as a singleton object as it can have a global point of reference and for all future calls to the cache object, the client application will use the in-memory object.

Previously published at http://www.vishalchovatiya.com/singleton-design-pattern-in-modern-cpp/

Tags

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

The Perfect Designing Tool Ipo For C

Source: https://hackernoon.com/design-patterns-singleton-pattern-in-modern-c-d0253uqz

Posted by: cappsandiflamboy.blogspot.com

0 Response to "The Perfect Designing Tool Ipo For C"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel