The Monostate pattern with default interface implementation in C#

Posted by : on

Category : C#

Introduction

Sometimes there are situations when we need to have only one instance of a class. Foe example, in a game we want to have only one instance of the Game Manager. If there is a possibility for a second instance to be created our game will crash or behave in unexpected ways.

Although that is the primary usage of the singleton, it is also useful to provide a single access point. With a singleton we can access variables from any class in our program. Although the singleton can be accessed by using the Singleton.Instance static field, its use is obvious to everybody. Whenever someone uses our singleton class, knows that it is a singleton.

Besides that, if we ever need to change the singleton’s implementation, we have to go inside every class that uses it and change the code. That is error prone and takes time.

Finally another problem with the singleton, is that we cannot change implementations at run time. Let’s see how the monostate pattern can help with these problems.

The monostate pattern

The monostate pattern doesn’t solve all the problems of the singleton pattern and it can be abused, just like the singleton, to eventually become a god object if we are lazy to think about our architecture. But it solves the above three problems that the singleton has.

The idea and the implementation is simple, just like in the singleton pattern. The singleton class ensures that there is only one instance of the class, by checking if an instance has already been created. If it has, it returns that instance, if not a new instance is created.

The monostate allows many instances to be created, but all those instances, point to the same place in memory. That means that all the monostate instances, behave as a single object. This is achieved by making all the variables of the monostate static. Now all the instances have the same variables.

Even if all the variables are static in the monostate, they are private and any access to those variables is made through methods or in the case of C# properties, which are actually an easy way to write a getter and a setter method in our class.

The singleton enforces the existence of only one object, but the monostate enforces that all its instances behave as a single object.

Let’s see a simple implementation:

Implementation

public class Monostate
{
   private static int _level;
   private static float _damage;

   public int Level
   {
      get => _level;
      set => _level = value;
   }
   public float Damage
   {
      get => _damage;
      set => _damage = value;
   }
}

No matter how many instances of the Monostate class we create, the _level and _damage variables will always be the same. For example the following code:

Monostate monostate = new();
monostate.Damage = 5.2f;
monostate.Level = 1;

Monostate anotherMonostate = new();
Console.WriteLine("Anothermonostate level: " + anotherMonostate.Level);

anotherMonostate.Level = 2;
Console.WriteLine("Monostate level: " + monostate.Level);

will have this output:

Anothermonostate level: 1
Monostate level: 2

This implementation solves the first problem that I described at the beginning of the post. The consumer of the monostate doesn’t have to know that it is a monostate. He is just creating an instance of the class as always and the singularity is achieved by behaviour. Let’s see now how we can solve the other two problems. How to easily change the implementation of our Singleton and how we can swap implementations at run time.

Usage with default interface implementation

By using default interface implementation, we write the following interface:

public interface IMonostate
{
   protected static int _level;
   protected static float _damage;

   public int Level
   {
      get => _level;
      set => _level = value;
   }
   public float Damage
   {
      get => _damage;
      set => _damage = value;
   }
}

this interface is actually our monostate, but of course we cannot instantiate an interface, we have to implement it:

public class GameManager : IMonostate
{ }

This is enough for our example. If you noticed it, our static variables are now protected. This way they can only be accessed by classes that implement our interface and not directly. Obviously there is no need to have default implementations, we could require that the implementors of the IMonostate interface implement everything. This way though, we can now create classes that implement the IMonostate interface that override only the behaviours that we want changed from the default.

For example let’s say that we need a test manager, that we want to use only for testing our game:

public class TestManager : IMonostate
{
   int IMonostate.Level
   {
      get => IMonostate._level + 3;
      set => IMonostate._level = value;
   }
}

This will suffice. Now our manager adds three more levels whenever we need. You should also notice the explicit implementation of our property. The reason is that with the explicit implementation, we have access to the Level property only by declaring our classes as IMonostate. If we try to declare and instantiate our TestManager like this:

GameManager manager = new();

we won’t have access to any of the IMonostate properties and by extension to our static variables. This helps, if we ever need to change our manager with another class that implements the IMonostate interface, by reducing the amount of code we have to change as everything will be of type IMonostate.

We don’t even have to use the same manager in all our code. Some classes can use the game manager and others the test manager depending on our situation. For example one class could instantiate the game manager like this:

IMonostate gameManager = new GameManager();
gameManager.Damage = 5.2f;
gameManager.Level = 1;

Console.WriteLine("gamemanager level: " + gameManager.Level);

which will return the value 1, but another could instantiate it like this:

IMonostate testManager = new TestManager();
Console.WriteLine("testManager level: " + testManager.Level);

which will return 4.

This can be used in different scenarios. For example we may have some base damage for our game and we want to see how certain enemies react when the do double damage. All we have to do, is change the new GameManager() with another game manager that returns the base damage multiplied by two.

This can also be used with conditional compilation, a game manager in our development and testing of our game and a different in production.

Finally, now our game manager can use polymorphism so that it can change at run time, depending on our needs.

Conclusion

Although monostate achieves the same results as a singleton and has some advantages over it, it also has some disadvantages compared to a singleton.

If performance is important, all those creations and destructions are not as efficient as having the single instance of a singleton. Even if the singleton has a performance cost, because of the if statement that checks every time if an instance exists, creations and destructions can cost more.

Also if there is a memory space concern, all those static variables occupy memory even before the first instance of the monostate is created.

But most importantly the disadvantage of the monostate pattern is the same disadvantage the singleton has. It is very easy to abuse it, become lazy with our architecture and add everything to our monostate so that we can have easy access. Eventually we will end up with a god object that everything inside is a mess.

A singleton or a monostate is not to be used as child’s toy box. When I was a kid and my room was a mess, whenever my parents told me to tidy up, I would take all my toys and put it inside a big box. The problem is, that everything is still a mess, the difference was that no one could see it.

But the next time i needed a toy to play with, it was so hard to find, that I would empty most of the box in my room and the room would be a mess again. Don’t treat your singletons and your monostates as a child’s box for toys. Use good architecture to define dependencies and have a tidy codebase.

Thank you for reading and as always if you have any questions or comments you can use the comments section or contact me directly via the contact form or by email, also if you don’t want to miss any of my new blog posts, you can always subscribe to my newsletter or the RSS feed.


Follow me: