Using C++ and all Sorted in Classes. How best to add something simple? [Namespace / Class(Singleton/Or dependency injection)]?

So instead of asking Al about that, I try here to hope to get some different insights.

I wanna try and keep it short to the point.
My baseproject is pretty big and maybe complicated(Toolkit to make Projects), and works with DI(dependency injection). (5k lines+)
Not the topic here, but related.

I was making a simple Buzzer class.(So simple not worth talking about).
But I maybe intend to make some stuff public, drivers, diff stuff, and in the end the whole toolkit.

This Buzzer is regarded hardware, and is/should be independent of my project.
So the question, how to best integrate it(and make not too complicated for release), so it doesn't pollute global space, or do you guys global just everything?

Case A: (Buzzer/Class STATIC)
(QUESTION 1):
When the class(Buzzer) is static(=no internal state it needs to save)
It is pretty simple:
namespace or static use: ?? (QUESTION 1)
Buzzer::ON or Buzzer::Melody1.

Case B: (Buzzer/Class NOT STATIC)
(QUESTION 2):
I just realized the simplest way is to do it like other libs. Just import where needed, and make an instance.
But this end up with more instances. So let's pretend I would need more instance control.
The question
So Singleton or DI? (QUESTION 2)


More extended explanation:

I have two layers:
-myEsp32 (takes care of instances of all hardware INSIDE the esp32(i2c, pwm, BT,Wlan etc)
User just grabs it all with one command, and can be used class like:

-myEsp32.pwm.dutyCycle(50)
-myEsp32.ic2.write(message)

The second layer:
Is basically the User/Project layer.
The user just got the instance of myEsp32 and inserts all modules with myEsp32 if they need it.
example:

    SensorManager sensorManager(_myEsp32);
    sensorManager.init();

    GrowController growController(_myEsp32);
    growController.init();

    DataManager dataManager(_myEsp32, sensorManager, growController);
    dataManager.init();

I could use Dependency Injection as in my baseproject, but that would mean, less clear to others, if I would make it(or something similar) public?

So then I could also use Singletons, which would get rid of DI, and makes sure only one global instance exists. But less clear, who owns what, but simpler.

This could be applied to a buzzer for example.(It has a simple getInstance() function).
There could also be made a good case, to use that for myEsp32 as well, since it is a hardware instance, their should be only one!?
But I heard that singletons are considered bad practice, cause more difficult to unittest. (although I did make mock functions to test stuff)

So DI or Singletons where possible, or even mix?

Or are there better other options? :see_no_evil:

So your question really is about DI or Singleton?

They are both design patterns used to manage object creation and dependencies in software development.

You should use Dependency Injection when you want to decouple your classes from their dependencies, making it easier to swap out implementations, especially in large and complex systems. It’s also ideal when you need your code to be testable and maintainable by injecting mock objects or fakes for testing purposes.

And you should use Singleton when you need a global resource, like a configuration manager or logger, to be shared across your application, ensuring there is only one instance of the resource. It’s suitable for situations where managing multiple instances of the resource would be unnecessary or undesirable.

For your buzzer example, You need to decide if your framework will support only one buzzer (where a Singleton pattern is appropriate) or multiple buzzers (where a Factory or Dependency Injection pattern would be better). There are games with multiple buzzers.

Hi @quantummagic ,

I went on and read your post at least twice, and after starting the third round I was still feeling something itching: the lack of some precision or decisions still for you to make.
And I quoted the part obove just to illustrate that feeling I have: you're stating you developed a class (So simple not worth talking about) and immediately go into questions about it: how to best integrate it, make it not too complicated for release, etc, etc.

  • My first advice would be: go on developing the idea of what YOU want your project to be, including the intended audience, including the possibility to be just for yourself.
  • Next and related: you're talking about systems, and systems are all about POV, define and set your point of view, that will set the path for you to follow.
  • The great work of the GoF about Design Patterns must be considered as a taxonomic classification of existent reality situations and how to solve them after you identify your problem with one of the proposed patterns. As discredited as it might be, the Singleton is one of those patterns, so if the situation you're dealing with is identifiable by you as a Singleton and want to use the proposed solution for that pattern is not less valuable than others... as a matter of fact, state machines and singletons have a strong relation... Singletons are as bad a practice when they are not the best suited solution to your problem, as is the misidentificaton of other patterns.

So, maybe if you narrow your project or design objetives it will be easier for you to identify the path to follow.

Good Luck!
Gaby.//

Well in the end it boiled down to it...yes. :sweat_smile:

A lot of things would fit the singleton pattern, since there could only be one hardware instance. i2c, bluetooth and so on, for example.
I don't really need unit testing. Right now I only used Di, and only one singleton for my perf_ticker (Only one should exist)

I identified this as the root of the problem beforehand, got also confirmed by AI and now you.

That speaks from my heart. That's why I am making that post here.
I am trying to make the spagat, using advanced techniques, but on the other hand, simple enough for beginners.

So how do other libs solve that?(having a state)
They just offer a class, and the rest is for the user.(I could do that for release of the lib, and my implementation is up to me.

Or I could also offer more a API like usage:
(Singleton AND/OR several instances if user wants)

class Buzzer {
public:
    // Static access like a singleton
    static Buzzer& instance() {
        static Buzzer defaultInstance;
        return defaultInstance;
    }

    static void alarm1_silent_ON() { 
        instance().doAlarm1SilentOn(); 
    }

    static void alarm1_silent_OFF() { 
        instance().doAlarm1SilentOff(); 
    }

    // Allows manual instance creation (if needed)
    Buzzer() {} 

private:
    TaskHandle_t alarm1_silent_taskHandle; // Instance-specific task state

    void doAlarm1SilentOn() { /* Start buzzer task */ }
    void doAlarm1SilentOff() { /* Stop buzzer task */ }
};

case A: (singleton):
Buzzer::instance().alarm1_silent_ON();

case B: (multi instances):
The user just uses it normally and can create as many instances he wants.

Buzzer mySecondBuzzer;  // Independent instance
mySecondBuzzer.alarm1_silent_ON();

This would make the class more complicated for some audience, and I never really saw a library doing that.(have not looked at many)
But it would solve all problems, AND also allows the user to call it from a STATIC function of the user. Some people on embedded might program only with static stuff?

But that class needs state(saving of taskhandle), cause the buzzer creates tasks, so not to be blocking.

Hum… doesn’t the ESP32 have two I2C hardware peripherals and support for software I2C for additional buses?

Yes. Maybe bad example.

I would never assume a single instance of any hardware.

Also, safe to say whatever framework you choose for v1, something will come along to upset the pattern and require a modified framework.

IOW, keep it simple. You can always make it more complex later. The reverse is harder.

Hi @quantummagic ,

Once again you seem to be far from centering the aim, and I believe each of us will have an opinion based on our own past development experiences and needs.

Are your doubts related to your wish to make your library "as popular as possible"? Because you talk about beginners, leaving to the user, etc, etc...

I'll just tell about one (of several different) experience: a development I was doing just got extremly complicated from the start, and it was about just three pushbuttons. At least two of them had to show some behavior, the last one was different. So I started developing a class for those two buttons, as both were the same, they would be two instances of the same class... but the behavior was pretty complex as I saw then, so I started developing a base class, then a sub-class that inherited the first and added some of the more complex members, and then another over the second... In that case it seemed to make sense as those sub-classes represented a kind of switch that existed as electromechanical devices, so they could be instantiated and used if somebody wanted to.
Going to the point, I ended up with a library with 12 classes (you asked about a class in every library or not), 38 methods (as long as I remember) (so there you've got my opinion about giving access through the API), some abstract classes and some instantiable, but the thing is I continued the development to the point I needed it... If somebody else benefits from it ok, I gave enough API... but it was a side benefit, and I know there are a LOT buttons libraries out there, but none had modeled the class I needed.
Oh, and in the way I killed the COI principle, abused the KISS and so on...
So... It's on you!!

Good Luck!
Gaby.//

How about stuff that really exist only once? like wlan module inside my esp32.
Or the myesp32 class itself? It manages all hardware instances normally internally via DI.
But the myEsp32 could be made singleton, since it is kind of an HAL.
But it has components like i2c-Manager, that can give several different instances
of i2c-Controller to the user. Same with pwm-Manager/Controller.

Or stuff like my TaskManager. I can't see any possibility besides unit testing, where more than one instance makes sense.

Right at the start of my journey, AI suggested a Base Sensor class.
My intuition told me, that this abstraction would not really help me, and I decided against.
Much later I saw a video about that topic, and I am glad, I decided against it.(Would not have been that dramatic to change, because my case is probably simpler)

That is the general problem with the OOP approach,..it is supposed to make stuff easier and modular, but reality looks different.
I see the value more for bigger projects, where several people work on it.
And as you said, I realize more and more, that you have to find your own way.
I like the way game devs handle that stuff. They seems to find a good compromise from both worlds.

I would argue that if the outcome is poor, it means the OO was poorly thought through…

Well I am not the first one saying that, this is discussed for some time, by people who are clearly more qualified than I am.
But you seem to say, it's just skill level?
ok.

I am saying that a well-designed object-oriented structure provides clarity and reusability, but forcing everything into classes can lead to unnecessary complexity.

Some developers create poor hierarchies and blame OOP, while others, for example those transitioning from Java, try to model everything as objects, even when a simpler approach would be better.

In C++, OOP is a tool, not a rule—use it where it makes sense, but don’t hesitate to write plain functions, free-standing utilities, or leverage what the language brings like templates and lambdas when it leads to cleaner and more efficient code.

Hi @quantummagic ,

"Au contraire, mon ami" would say a famous character in a movie, your case is much more complex than a simple pushbutton becoming a specific self through inheritance.

I respectfuly disagree with that point of view. After decades of following different development paradigms I would simply refuse to participate in any development requiring to leave out OOP.
I admit it might become harder and heavier in the analisys stage, and if you get a class design wrong from the start it'll make things difficult to work, until you get that's the problem and go back to rework that class... the rest of the development process will repay your investment clearly.

But once again, it's systems design, your personal POV must be the one that makes you as productive as you can be!

Good Luck!
Gaby.//

"Ah, mon ami, as Poirot himself would say, complexity is in the eye of the beholder! Some choose an inheritance tree as tall as the Eiffel Tower, while others prefer a well-placed bridge. In the end, everyone architects the complexity they deserve—or at least, the one they can live with. The true mystery is whether it was by design... or accident!"

So how do you handle microcontrollers in private and/or business?

I heard, that most microcontroller programmers are more the Oldschool C-procedural type of programmer. Some people would even say "messy" or "chaotic", and interpretable mostly by him only.

I must say, that debugging seems easier(bought esp-prog, but never used), because of all the separation of concerns, just to set it up in the first place. But I see, how several layers of dependencies can be problematic.

IME There are two quite kinds of MCU programmers.

  1. Those who use C grudgingly, and would probably be quite happy doing assembler. But they do know a lot about electronics and mechanical systems. Trying to persuade them that judicious use of C++ would be a benefit is quite hard. Code golf is their preferred exercise.

Real life exchange "performance is not an issue, so why did you rewrite my code in assembler?" "I just felt like it"

  1. Those who formerly used Ada, Java, Python etc, who can't understand how you can get anything into such a tiny place. Trying to persuade them not to allocate kilobytes of RAM just to flash an LED is quite hard. But they do know all the latest design patterns.

Real life exchange "There is only 2K bytes of RAM", "You mean 2 Mega-bytes?" "No, I mean 2k..."

Caricatured extremes perhaps. But finding people who can fill the Goldilocks zone (a little OOP, but not too much to impact performance) is somewhat less common.

Hey bob!!
That was a pretty entertaining post, loved it!! :rofl:
I seem to enjoy the first group a bit more, that's why I stopped programming, because I learned phyton. I can't bring it to the heart to write slow code.
Because of the hungry 3.5 tft touch it would even make sense, but even if not I would do it.
After my performance tests with reasing all sensors every millisecond, handling the control logic and collecting a whole systemsnapshot, and saving to sd-card via 6 buffers, I only used 10% of ONE core.
But I still thought: Binary aligned in memory is quiet nice, but how about SIMD? lol.

My first program 15 years ago in C++(have forgotten all) was a montecarlo simulation of a oddity calculator in poker.
There was a official .dll for that.
This was my first program, and I started with pointers and direct memory manipulation and a 120mb lookup table.(same table as the .dll)
It was like 8 times faster than that .dll!
I could simulate 28 BILLION flops per second(the other flop) for several players.
This was 15 years ago, on a already outdated computer at that time.

Later I learned phyton, and made a multithreaded graphical windows-like file explorer that also creates thumbnails for pictures and video, several times faster than windows.

But knowing I work with added "handbrake" I lost interest a bit.

and yes I have watched several assembler videos lately, I am drawn to it.

That's what I try to accomplish with my toolkit for esp-idf.

Hi @quantummagic ,

It would be too easy to imagine my age if I tell you I programmed a little "lower level" than assembler?? We wrote the code in assembler, converted the instructions to the corresponding hexadecimal opcode instruction and fed it to the micro with an hexadecimal keyboard!! I believe the thing the university gave us was called a Intel sdk 85 development kit. The following year we worked with an original IBM PC... it was completely disassembled chip by chip in a desk, so I come from there, worked with assembler... and even after going all the long way including through the original C I feel happy and comfortable with C++.
And of course, there's a lot of (procedural) coding in the middle, but the benefits are too many to return.

And I agree with @bobcousins in general. Balanced great opinion.

Good Luck!

Gaby.//

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.