Master C#

How to avoid temporal coupling in C#

Two approaches to solving temporal coupling, a 'design smell' that results when members of a class must be invoked in a specific sequence

How to avoid temporal coupling in C#
Thinkstock

Master C#

Show More

Two hallmarks of good software design are low coupling and high cohesion. Cohesion is a measure of how focused a module of a program is on a set of responsibilities. Coupling is a measure of the degree to which the modules of a program are dependent on other modules.

Giving a module a broad set of responsibilities, or making it dependent on many other modules, makes the code more difficult to maintain and discourages code reuse. In other words, low cohesion and high coupling are detrimental to your application. Thus we should strive for high cohesion and low coupling: Restricting the focus of modules to closely related functionality, and minimizing their dependence on external code. 

I have discussed the various types of cohesion and coupling in a previous article (see “Design for change: Coupling and cohesion in object oriented systems”). In this article I will look specifically at temporal coupling and suggest some strategies to avoid this particular design smell. 

What is temporal coupling?

Temporal coupling is a “design smell” that occurs when you have an implicit temporal relationship between two or more members of a class. This generally means having to invoke multiple members of a class in a specific sequence. The members are effectively tightly coupled because the calls must follow a strict order.

Let’s look at an example. Consider the following class, called FileLogger, which contains two methods named Initialize and Write.

public class FileLogger
    {       
        public void Initialize(string fileName)
        {
            //TODO
        }
        public void Write(string message)
        {
            //TODO
        }
    }

Note that I’ve skipped the method definitions here. While the Initialize method is called to set the filename and path, the Write method is used to log messages to a text file. These methods have to be called one after the other, i.e., in a pre-defined sequence, because the Write method needs the filename of the text file where the messaged will be logged. If the file name is not set in the Initialize method, an error will be thrown at runtime when the application tries to write text into the file.

The code snippet given below illustrates how these two methods should be called in a sequence.

static void Main(string[] args)
        {
            string fileName = @"D:\IDG\Sample.txt";
            FileLogger logObj = new FileLogger();
            logObj.Initialize(fileName);
            logObj.Write("Hello World!");
        }

Solve temporal coupling with constructor injection

One solution to temporal coupling is to use constructor injection. Let’s refactor our FileLogger class and implement constructor injection. Here’s the updated version of the FileLogger class for your reference.

public class FileLogger
    {
        private readonly string fileName;
        public FileLogger(string fileName)
        {
            if (string.IsNullOrEmpty(fileName))
            {
                throw new ArgumentNullException(“File Name”);
            }
            this.fileName = fileName;
        }
        public void Write(string message)
        {
            //TODO
        }
    }

Note that our FileLogger class now contains a private read-only string variable that represents the name of the file where the text messages will be logged, and the value of this variable is set in the constructor. We also check for a null or empty value and report it appropriately. In doing so, we ensure that the filename is always available to the Write method while eliminating the need to call two methods in sequence.

Solve temporal coupling with an abstract factory

Another approach to avoiding temporal coupling is to use an abstract factory. Note that the abstract factory design pattern is a creational pattern that can be used to decouple your application from the way class instances are created. Here is a version of our FileLogger class that uses an abstract factory. The FileLogger class now implements the ILogger interface.

public interface ILogger
    {
        void Write(string message);
    }
    public class FileLogger: ILogger
    {
        private readonly string fileName;
        public FileLogger(string fileName)
        {
            this.fileName = fileName;
        }
        public void Write(string message)
        {
            //Write code here to log message(s) to a text file
        }
    }

Note how our factory implementation decouples the creation of the log file from the logger implementation. Because the filename variable is read-only in the FileLogger class, its value can only be changed in the constructor.

Below you can see the LoggerFactory class that implements the ILogger interface.

public interface ILoggerFactory
    {
        ILogger Create(string name);
    }
    public class LoggerFactory : ILoggerFactory
    {
        public ILogger Create(string fileName)
        {
            if (string.IsNullOrEmpty(fileName))
            {
                throw new ArgumentNullException(“File Name”);
            }
            return new FileLogger(fileName);
        }
    }

Finally, the code snippet given below illustrates how all of this works. Note how the invariants, i.e., the log file and the log message, are protected by the LoggerFactory and the FileLogger classes.

static void Main(string[] args)
        {
            string fileName = @”D:\IDG\Sample.txt”;
            LoggerFactory factory = new LoggerFactory();
            var logObj = factory.Create(fileName);
            logObj.Write(“Hello World!”);
        }

Temporal coupling occurs when you have method calls that need to be invoked in a particular order. In essence, if you have a set of activities in your application that are strictly ordered, i.e., the results of one or more activities are needed in subsequent activities for the application to function properly, you may say that such methods are temporarily coupled. Note that the same applies to properties as well, i.e., you might have some methods that should be preceded by some property calls because the results are needed by subsequent methods.

Copyright © 2017 IDG Communications, Inc.