It's time for Mono.
This is a design pattern I found, and then later found this name for. When I originally found the pattern I was calling it a
WrappedSingleton because... it's a
Singleton that's "wrapped" in a class that must be instantiated to access the singleton aspect of it. A "Wrapped Singleton".
MonoState is better. The object has a single state across all instances... mono... state. TAH-DAH!
Why not singletons?
Because they are horrible and you're terrible for asking!
Also because they make testing the code HELL. So.. no. There's lots of problems with a singleton; and you get the same value; plus SO SO SO MUCH MORE' from a MonoState version.
I'm not really going to get into this, if you think you should use a singleton, go unlearn that.
TANGENT: I look at a lot of resumes. I can ALMOST guarantee I will never say "yes" to someone with a resume that states they are experts in design patterns, 'like Singleton'. If you are, that isn't gonna be listed.
The earliest example of this I created is now called
This is definitely an updated version of the concept. The exact functions that are implemented vary project from project. The core idea is the same. Have a class
MonoStateHttpClient that has a static
HttpClient. This class implements an interface, which I have
IHttpClient. This allows tests to provide a fake with ease.
Testing is one of the huge boons for using a MonoState w/interface over a singleton - you can actually test it; without shenanigans.
I have this set of functionality in every version
#if DEBUG public static void TestSet(HttpClient testInstance) => s_httpClient = testInstance; #endif private static HttpClient s_httpClient; private HttpClient MonoState() => s_httpClient ??= new HttpClient();
This allows me to set the test client to be used regardless of how far down the stack the actual instantiation of the
MonoStateHttpClient is. This does create A LITTLE issue since we're setting a static and tests won't always play nice. Absolutely. It's more of an "end to end" type of test tool. Ensure the entire code works as expected from the entry point to where it would otherwise make an http call. You can control responses and behaviors this way to ensure end to end is happy.
We used this A LOT in the UWP project from years ago. It's where this first appeared. We were able to run end to end tests on everything between the UI (starting at a screen load) to the network call (faked) and back up to ensure the correct information was returned/UI called. We developed A LOT of the app never making actual network calls. We'd configure the response and able to do all of our testing like that.
MonoState() method is used to get the instance. That way the check only happens in one place and is good to go after.
#if DEBUG is there to prevent release builds from setting the client. Downside... the project has to be built in debug mode to be able to be tested. Small trade off for ensuring we don't shoot ourselves in the foot.
I recently realized the
ConditionalAttribute could be used here. Shifting this to be
[Conditional("DEBUG")] public static void TestSet(HttpClient testInstance) => s_httpClient = testInstance;
will cause the compilation to MSIL to "ignore" the method. Removes the call from the executed code. It's still in the release binary, unlike the
#if version, but won't be called unless there is a pre-compilation variable
I've started to use this in a few other places. Particularly in the SDK I'm working on. It's great to enable behavior and logging for clients during development that is not going to happen in release builds.
I also do the inverse. I have telemetry that I want from the SDK, but not in debug builds. I have those methods with
[Conditional("RELEASE")]. It's pretty neat.
MonoStateHttpClient is the first; but I have others. I'm sure you can think of plenty of ways it can be used. Let's check out the other ones.
Our configuration values are often accessed through some form of a singleton which can cause some tight coupling to having to get those values perfectly defined to test effectively... and not stomp on variations to that config value.
MonoStateConfig can help with that! There's actually not a lot of places I use the
MonoStateConfig. In production code, there's one place; the
StringConfigurationValue. That tends to be nearly the only place. Some tests will use it to define a value for for tests against something extending one of the
*ConfigurationValue classes... but outside of that, we don't need to see it. We encapsulate the knowledge of HOW to access the configuration values to one place... almost a Single Point Of Truth...
This also has a "set" method, but no guarding. This class needs something to actually SET the configuration into it. A single line in the startup of the app.
I didn't think I had this one in the repo initially; but it's part of the
Web.MtgDiscovery project, so it almost got missed. This is the
MonoStateMemoryCache. Unsurprisingly, it wraps an MemoryCache. You don't get to set this one for tests. You can just instantiate it, set the value you want in the test, then execute the behavior. Pretty simple in it's test usage. For classes that take an
IMemoryCache, you give it a fake and don't have to worry about static values.
Finally I'll point out the
MonoStateCosmosClientAdapter. Any adapter like this is definitely one that should be considered for a
MonoState version. Even if it has some internal mechanisms to reconnect.
This isn't particularly impressive... Just provides us a mono state for all consumers to share.
This one isn't a monostate, but is does use the "debug set for tests" pattern. My AzureSqlConnection.
The sql connection isn't a monostate because it needs to open/close for every connection. At least that's my understanding of it. You'll see the use of the
[Conditional("DEBUG")] here to have additional logging during development.
That's all I really have around the mono-states. It's a fantastic pattern for resources that should be minimal in their instantiation. Singletons are an anti-pattern and what they attempt to solve STILL needs a solution; a better solution. Which is what I use the MonoState pattern for.