12

I encountered the following situation: I have two modules, TokenService and Wifi which are initialized from main(). The modules themselves don't know of the existence of each other and function completely independent, yet they need access to the same global resource which in this case is the nonvolatile storage flash, or short nvs, which must be initialized for the module to function correctly. The flash initialization function however is not idempotent, this means I can't call it twice, yet it should be called at least once when the first of either module goes through initialization. Here's a visualization of the thing:

Two modules accessing same global resource

I have not yet found a suitable elegant way to accomplish this. My current approach is to defer the initialization of the global resource back to the user of the module via a lambda, but it's not satisfying. I imagine that this is not the first time anyone has encountered this problem so I'm looking for the idiomatic approach to solve this.

1
  • Referring it back to the caller via a lambda is, at the very least, one of the idiomatic ways of doing this.
    – jmoreno
    Apr 29, 2023 at 1:28

4 Answers 4

43

The answer to the problem "a third-party module has unsuitable semantics..." is always "...so I code an adapter that warps those semantics into something suitable." In other words, write e.g. a façade which does nothing except remember the state of initialization, and forwards any actual calls to the actual module.

3
  • 5
    Adapter, Facade, Proxy, and Decorator patterns are all closely related and used to solve similar problems in slightly different ways (according to your requirements). All worth learning about!
    – SnakeDoc
    Apr 26, 2023 at 17:06
  • FacadeSingleton. -_- Apr 27, 2023 at 5:05
  • 3
    @SimonRichter the nvs is a singleton object basically
    – Jimmy T.
    Apr 27, 2023 at 11:41
36

There are several ways to solve this problem.

One is to use an adapter or facade around the nvs, as described in the answer by @KilianFoth.

Another option is to use dependency injection. Then the contract of both TokenService and Wifi can state that they expect to receive a ready-to-use object that will handle their storage needs. That object could be the single instance of nvs, which has been initialized by main, or it could even be a storage mock that is provided by the test environment.

9
  • 5
    I would prefer the second one in this case. Of course, if you still need the lazy init, you can pass a lazy-init adapter or facade to both TokenService and Wifi.
    – user253751
    Apr 26, 2023 at 15:33
  • Dependency injection is my preferred solution. Not least of which it makes testing easier but even better it makes the entire system more configurable for future needs.
    – slebetman
    Apr 26, 2023 at 22:59
  • 3
    Isn't dependency injection orthogonal to the facade question? You could inject an instance of the original library, and that wouldn't solve the problem at all, iiuc. Or you use a concrete facade class without injection, which solves the issue, just with worse testability. Apr 27, 2023 at 8:15
  • 3
    @Peter-ReinstateMonica a "pure" dependency injection solution would be to initialize the adapter in main (or in a container) before instantiating either module. It would lose the benefits of lazy init obviously.
    – Rad80
    Apr 27, 2023 at 10:56
  • 8
    @Peter-ReinstateMonica The fundamental issue is that Wifi and TokenService each don't know about the existence of the other, so it's hard to make them collaborate on who should initialize nvs exactly once. But if they both take an already-initialized nvs instance as a parameter, then it's their caller's problem. Their caller is main, which does know about both Wifi and TokenService, and if we're doing dependency injection it also knows about nvs. So main can coordinate initializing nvs exactly once and then passing it to both modules.
    – Ben
    Apr 28, 2023 at 1:30
0

Why are you so concerned about Wifi and TokenService not being aware of each other while they're allowed to know about implementation details of the storage module?

Only let each module know what they actually need about the storage and leave everything else about the manufacturer module inside a module of your own whose only job is bridging the gap between what your modules need and what the manufacturer module offers. As other answers say, there are pleny of patterns to do that.

-1

This looks like use case for a singleton.
Forward all the calls to flash through a singleton - the first time it is accessed, it will handle the initialization logic, and stick around until program exits.

3
  • Not, kinda. nvs already is a singleton by OPs very definition. Their question is how to get two modules to actually use this singleton as one
    – Hobbamok
    Apr 28, 2023 at 8:57
  • @Hobbamok through a Singleton instance that acts as gateway and handles the initialization.
    – Thomas
    Apr 28, 2023 at 15:00
  • 1
    Singleton's are (generally speaking) an anti-pattern. They create spaghetti code with hidden dependencies. It is better to use dependency injection.
    – Questor
    Apr 28, 2023 at 18:19

Not the answer you're looking for? Browse other questions tagged or ask your own question.