Singleton - When should they be used for good software design?

Continuing the discussion from New application note for using the singleton pattern:

Brief: This is a fantastic guide… but I’m wondering when the Singleton pattern is best used. In particular, is what I’m doing the intended use of Singleton or if I’m over applying it. I also wanted to get feedback on using #define to make the code more readable while using Singleton based on the Application Note.

Background: So I’m finally biting off some over due code refactoring work. After a few years of development/building I had a big monolithic 1800+ lines long .INO file that contained everything. It was still broken down into Setup(), Loop(), finite state machine and then various functions the FSM would call. So it had some organization but was all contained in the same INO file making it daunting at times. In comparing notes with @chipmc in his code structure and also trying to “level up” my C++ knowledge, I am wanting to break out the code based on it’s core functionality into several .CPP/.H files including. Each one containing the data, methods/functions associated with that type.

  • Pinout (Defines GPIO pins and initializes all pins with pinMode())
  • Config (Handles reading device configuration data from EEPROM and a structure to store config data) centrally.
  • Sensors (Handles reading data from any sensor and initializing sensor config)
  • Actuators (Handles the control of any actuator/relay etc.)
  • LoRa (Handles all things LoRa radio)
  • Timing (Handles all things regarding setting time, calculating time to next report window)
  • etc.

I started breaking it out in various .cpp/.h files and a few different data structs just for improved management and organization of the code. As I played with a few libraries and learned a bit about classes, I thought, that was a really nice way to have a structured organization of the data and various functions/methods. So I went ahead and created a class for pinout, class for sensors, class for actuators, class for LoRa, class for timing, etc. I then read about Singleton per this earlier blog post and realized that’s effectively what I’m trying to do. So I then updated it all to use the near exact format using the code generator (a great feature/tool).

So my first question… is this the intended use of Singletons one for each: pinout, config, sensors, actuators, lora, timing, etc. or am I over applying it?

Second question… is this an OK method to make the code more readable:
The only part I didn’t like was requiring “ClassName::instance().method()” and/or “ClassName::instance().variable” anytime you used a variable or method from the singleton class. This made the code a little less readable. As a way around this, I ended up using some macros/#define to swap out and preserve the 2-4 letter acronym I had prior. I.e. I use this:

#define sns sensors::instance()

Pure singleton on the left, less readable. After using #define on the right… more readable/quicker to code. Is this OK or is there another work around:

2 Likes

Any time you have exactly one instance of something in the app, and more than one module needs to access it, the singleton is ideal. Obviously if you have multiple instances of the class the singleton won’t work. And the singleton is better than a globally constructed class because of the ordering of global constructor initialization issue.

Normally I try to minimize the use of macros because they don’t enforce a type but using it to get the instance is reasonable. In fact, if you look at the Device OS source, you’ll see that’s what BLE is defined as:

2 Likes

Singleton is great pattern and probably the first one everybody learns to use. There are lots of design patterns that work in all object-oriented programming languages, so that might worth taking an online class or reading a book about.

If you like the syntactic sugar of the #define I think it’s fine and a generally accepted solution. Just remember that macros can have global impact and not always in a good way, so pick your macro names carefully.

3 Likes

Excellent… Thanks for the explanation and guess I wasn’t too far off on using a macro for code readability if it’s done elsewhere.

More generically, is breaking apart the code into various singleton class instances a reasonable approach to modularize the code base or should it be dedicated more for a specific hardware device such as BLE or is this just all personal preference?

Yeah, when I say I’m trying to “level up” my programming ability, this is what I’m after. I started with this YouTube video series a few weeks ago to better understand the foundations of C++. It’s been good so far: Welcome to C++ - YouTube

Are there other good reference material, courses, video content you’d recommend? I personally like the YouTube, Udemy, Coursera, LinkedIn Learning approach or at least it fits my personal learning style the best. I took several programming courses back in college (mostly Java). C++ has been mostly the school of hard knocks, hacking & piecemealing snippets of code together until it works. It’s been serving me well so far but maybe time to re-structure and level up the code base. Seems like I’m in the ball park of an acceptable code structure…

3 Likes