How To Avoid Temporal Coupling in C#

Posted by : on

Category : C#   Architecture

What Is Temporal Coupling

Temporal coupling is an implicit dependency that we create in our code, when there is a need for two or more different statements to be called in a certain order, otherwise our code will fail.

Reuse of our modules is a core concept of OOP. By creating our classes in a way that certain methods have to be called in order, we set ourselves for failure of our program at runtime. With temporal coupling, if we don’t call in order the statements that are supposed to be called in order, our program may crash at runtime, or even worse we create bugs that are difficult to find.

An important thing of coding is to keep our mental energy solving the problem at hand and not trying to remember certain data or rules that we need to follow, because of the way we have implemented certain classes. With temporal coupling, we reserve some space in our head, that we always we have to remember that certain statements will have to be called in order, otherwise our program won’t work. It is better to code in a way, that any form of temporal coupling won’t let our code compile, rather than make ou program fail or behave in a wrong way at runtime.

Let’s see some examples of temporal coupling:

public class Enemy
{
   private int _health;
   private int _damage;
   
   public void Initialize(int health, int damage)
   {
      _health = health;
      _damage = damage;
   }

   public void Attack()
   {
      Console.WriteLine($"Attacking and making {_damage} damage");
   }
}

Here we have to call the Initialize method, before any other methods. If we don’t, we introduce a bug in our code. Sometimes, in other situations, if the fields that get initialized are reference types, our program will crash with a null reference exception. I wrote this example, as it shows that temporal coupling can also introduce hard to find bugs. Anyone that creates an instance of the Enemy class and then calls the Attack method, without having first a call to the Initialize method, will have fallen victim of the temporal coupling that we have introduced in our code.

Sometimes, temporal coupling can also happen, when we need two operations to be called in order, but there is also a need that something happens between those two statements. For example let’s say that we have a requirement that in a game the chests are always closed. We may open a chest, add or remove items from it, but after that we have to close it:

public class Chest
{
   private readonly List<Item> _chestItems;
   private bool _isOpen;

   public Chest()
   {
      _chestItems = new List<Item>();
   }
   
   public void Open()
   {
      Console.WriteLine("Chest has opened");
      _isOpen = true;
   }

   public void AddItem(Item item)
   {
      if(_isOpen)
         _chestItems.Add(item);
   }

   public void Remove(Item item)
   {
      if(_isOpen)
         _chestItems.Remove(item);
   }

   public void RemoveAll()
   {
      if(_isOpen)
       _chestItems.Clear();
   }
   
   public void Close()
   {
      Console.WriteLine("Chest has closed");
      _isOpen = false;
   }
}

Here we have to remember, to call the Close method, after we call the Open method and perform any actions. Calling these methods inside each action doesn’t work, because we need to be able to perform any number of actions between one open/close sequence.

Let’s see some ways that we can avoid temporal coupling in our code.

Constructor Injection

For the first example, the easiest way, is to inject any values during the creation of our instance, to the constructor. We can rewrite our Enemy class, like this:

public class Enemy
{
   private readonly int _health;
   private readonly int _damage;
   
   public  Enemy(int health, int damage)
   {
      _health = health;
      _damage = damage;
   }

   public void Attack()
   {
      Console.WriteLine($"Attacking and making {_damage} damage");
   }
}

Problem solved. No Initialize method here. If an instance of our type is created, we can be sure that the values we need, have been initialized too.

That is not always possible though. In the Unity game engine for example, Monobehaviours can’t have constructors with parameters. If for whatever reason constructor injection is not possible, we need another way.

Abstract Factory

With an abstract factory, we can control the creation of our objects and make any initialization during that creation. Here is the code for the Enemy example, that uses the Initialize method.

First we create our interfaces, one for our factory with a Create method, and one for our Enemy class:

public interface IEnemyFactory
{
   public IEnemy Create(int health, int damage);
}

public interface IEnemy
{
   public void Initialize(int health, int damage);
   public void Attack();
}

then we create our Factory implementation and we make our Enemy derive form IEnemy:

public class EnemyFactory : IEnemyFactory
{
   public IEnemy Create(int health, int damage)
   {
      IEnemy enemy = new Enemy();
      enemy.Initialize(health, damage);
      return enemy;
   }
}

internal class Enemy : IEnemy
{
   private int _health;
   private int _damage;
   
   public void Initialize(int health, int damage)
   {
      _health = health;
      _damage = damage;
   }

   public void Attack()
   {
      Console.WriteLine($"Attacking and making {_damage} damage");
   }
}

Now the creation of our Enemy happens like this:

IEnemyFactory enemyFactory = new EnemyFactory();
IEnemy enemy = enemyFactory.Create(10, 5);
enemy.Attack();

Two things to notice here:

  • Our Enemy class has the internal modifier. This happens so that we can have our factory and the enemy in their own library. Anything outside that assembly, won’t be able to create directly any instances of the Enemy. That will be possible, only through the factory’s Create method.
  • In the Create method, we still call the Enemy’s constructor : IEnemy enemy = new Enemy(), but that can be substituted by anything that the framework/engine uses. For example in the Unity game engine, this could be a call to the Instantiate method, for creating a game object from a Prefab.

Template Method Pattern

The template method pattern, allows us to call statements in certain order and provides hooks that can be used to have the functionality we need between those statements.

Normally, if for example we wanted to add items to a chest, we had to remember to write our code like this:

Item item1 = new Item();
Item item2 = new Item();
Chest chest = new();

chest.Open();
chest.AddItem(item1);
chest.AddItem(item2);
chest.Close();

Let’s see an example for the Chest class, that solves our temporal coupling problem.

First we make the Chest class abstract:

public abstract class Chest
{
   private readonly List<Item> _chestItems;

   protected Chest()
   {
      _chestItems = new List<Item>();
   }
   
   public void Execute()
   {
      Open();
      Operations();
      Close();
   }

   protected void AddItem(Item item)
   {
      _chestItems.Add(item);
   }

   protected void Remove(Item item)
   {
      _chestItems.Remove(item);
   }

   protected void RemoveAll()
   {
      _chestItems.Clear();
   }

   protected abstract void Operations();
   
   private void Open()
   {
      Console.WriteLine("Chest has opened");
   }
   
   private void Close()
   {
      Console.WriteLine("Chest has closed");
   }
}

Now we can derive, from this class and override the Operations method with the operations we need to perform, without having to remember opening and closing the chest:

public class AddTwoItemsToChest : Chest
{
   private readonly Item _item1;
   private readonly Item _item2;

   public AddTwoItemsToChest(Item item1, Item item2) : base()
   {
      _item1 = item1;
      _item2 = item2;
   }

   protected override void Operations()
   {
      AddItem(_item1);
      AddItem(_item2);
   }
}

Now to add to items to the chest, our code looks like this:

Item item1 = new Item();
Item item2 = new Item();
AddTwoItemsToChest addTwoItemsToChest = new(item1, item2);

addTwoItemsToChest.Execute();

This way, we have more boilerplate code, but for a series of statements that involve methods of the Chest class, that need to be called frequently in our code, we eliminate any chance to create bugs, by forgetting to call the Close method, after every time we call the Open method to perform some operations.

For more on the template method pattern, you can check my post here

Composition

Obviously, if we don’t want to deal with inheritance, the above can also be done with composition with the original Chest class:

public class AddTwoItemsToChest
{
   private readonly Chest _chest;
   public AddTwoItemsToChest(Chest chest)
   {
      _chest = chest;
   }

   public void Execute(Item item1, Item item2)
   {
      _chest.Open();
      _chest.AddItem(item1);
      _chest.AddItem(item2);
      _chest.Close();
   }
}

Here is the code that gets us the same result:

Item item1 = new Item();
Item item2 = new Item();
Chest chest = new Chest();
AddTwoItemsToChest addTwoItemsToChest = new(chest);

addTwoItemsToChest.Execute(item1, item2);

The problem with this method though, is that anyone can create a new Chest object and try to call its methods, without calling the Open method at the beginning or forgetting to call the Close method in the end.

Ideally we should try to avoid that, either by making the Chest class internal in an assembly with the classes that need to use it, or with some other method that can try to emulate the friend functionality of C++. Some ways to partially emulate the friend functionality of C++ can be found in my previous post

Avoiding Shared State

Many times, the problem of temporal coupling can be avoided, if our class doesn’t have an internal state. For example in the original Enemy code the _damage variable is a shared state between the methods of the class.

We can try to remove that variable from the class and add it as a parameter to the Attack method. With this, our enemy doesn’t have to be initialized, but also won’t have its own damage. By creating a class that contains only data that represents the data of our enemy without any methods and then using that data as parameter to the Enemy methods, we can avoid any initialization for the class.

This works well, if we code in a way that we separate our types, that define behaviour from the data that this behaviour uses. But separating behaviour and data, is a whole new subject that probably deserves its own post.

Delegate Parameter

Finally we can use a delegate as a parameter, if we only have one operation that needs to be performed after another or between two operations. If for example, we knew that our chest can perform only one operation between Open and Close, but we wanted to have the option to choose any operation, not only those defined by the Chest class, then we could have made a method like this:

public class Chest
{
   public List<Item> ChestItems { get; }

   public Chest()
   {
      ChestItems = new List<Item>();
   }

   public void Operation(Action operation)
   {
      Open();
      operation?.Invoke();
      Close();
   }
   
   private void Open()
   {
      Console.WriteLine("Chest has opened");
   }
   
   private void Close()
   {
      Console.WriteLine("Chest has closed");
   }
}

Now we can use the Operation method that guarantees that the Open and Close methods will be called before and after each operation we want to be performed:

Item item1 = new Item();
Item item2 = new Item();
Chest chest = new Chest();

chest.Operation(() =>
{
   Console.WriteLine("Example operation");
   chest.ChestItems.Add(item1);
   chest.ChestItems.Add(item2);
});

Conclusion

Temporal coupling should be avoided, because at best will crash our program and at worst will introduce hard to find bugs. It can’t be caught at compile time, as the compiler won’t give us any warning, so we should always have to remember at the back of our mind a certain order that some methods have to be called.

By eliminating temporal coupling, we can free space in our head from things we have to remember and use it to concentrate in the code we are writing at that moment. We also make our code reusable in future projects by avoiding the need to look inside our classes for the implementation, as there is no way to indicate in our method signatures that certain actions have to be performed in a certain order.

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 the new blog posts, you can always subscribe to my newsletter or the RSS feed.


About Giannis Akritidis

Hi, I am Giannis Akritidis. Programmer and Unity developer.

Follow @meredoth
Follow me: