Design Patterns Software Engineering

Design Patterns – Simple/Static Factory

Pinterest LinkedIn Tumblr
The code is available on GitHub here

The main goal of the Factory pattern is to decouple the object creation from the client. This is a good practice because if the creation of the object is changed, we don’t need to change every part of the application that specifically creates the object, instead we have only one place to make modifications when the implementation changes. Moreover, every part of the application that needs the object, can use the factory instead of knowing how to instantiate it specifically.

In general, in order to implement a factory we use an instance create method that returns the proper implementation of an object, but another alternative is to use a static method instead. This is called a static factory and its purpose is the same as the factory described previously. The main advantage of a static factory is that we don’t need to instantiate the factory every time we use it, but the downside is that we cannot have subclasses of it and change the behavior of the create method. In this example we will use the static version.

A MessageDispatcherFactory example

In order to understand better the pattern, we will build a Logger that sends messages either to console or disk depending on the environment the application is running. For the purpose of actually writing the message, we create a new MessageDispatcher abstract class. We can use the Logger class to instantiate a MessageDispatcher, but we create a factory that handles the creation instead. In the following code the MessageDispatcherFactory encapsulates the object creation of all MessageDispatchers.

public class MessageDispatcherFactory
{
   public static MessageDispatcher Create(string type, string message)
   {
      MessageDispatcher messageDispatcher = null;

      if (type == "console")
         messageDispatcher = new ConsoleMessageDispatcher();
      else if (type == "filesystem")
      {
         string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "logs.txt");
         messageDispatcher = new FileSystemMessageDispatcher(path);
      }else
         throw new InvalidOperationException("There is no message dispatcher for type:" + type);

      messageDispatcher.SetMessage(message);
      messageDispatcher.PrepareMessage();
      return messageDispatcher;
   }
}

The MessageDispatcherFactory will create the proper instance of MessageDispatcher depending on provided input. Moreover, in case of required methods that need to be called during the object creation, the factory create method is the correct spot for calling them. In this example, the SetMessage and PrepareMessage methods are used as the required methods that need to be called in order to correctly initialize the MessageDispatcher.

Next, we present the ConsoleMessageDispatcher and FileSystemMessageDispatcher subclasses. They also implement the PrepareMessage differently. In ConsoleMessageDispatcher for example, we want to remove all excess spaces the message could have, but for the FileSystemMessageDispatcher we want to write the message without any other modification.

public class ConsoleMessageDispatcher : MessageDispatcher
{
   public override string Description => "I am a console message dispatcher.";

   public override void Dispatch()
   {
      Console.WriteLine(Message);
   }

   public override void SetMessage(string message)
   {
      Message = message;
   }

   public override void PrepareMessage()
   {
      Message = Regex.Replace(Message, @"\s+", " ").Trim();
   }
}

public class FileSystemMessageDispatcher : MessageDispatcher
{
   private readonly string path;

   public override string Description => "I am a file system message dispatcher.";

   public FileSystemMessageDispatcher(string path)
   {
      this.path = path;
   }

   public override void Dispatch()
   {
      File.AppendAllText(path, Message);
   }

   public override void SetMessage(string message)
   {
      Message = message;
   }

   public override void PrepareMessage()
   {
      // other formatting
   }
}

Lastly, the Logger is using the factory in order to obtain the correct MessageDispatcher instance:

static void Main(string[] args)
{
   var client = new LoggerClient("console");
   client.Log("a message");

   client = new LoggerClient("filesystem");
   client.Log("another message");
}

public class LoggerClient
{
   private readonly string _type;

   public LoggerClient(string type)
   {
      _type = type;
   }

   public void Log(string message)
   {
      var messageDispatcher = MessageDispatcherFactory.Create(this._type, message);
      messageDispatcher.Dispatch();
   }
}

Notice that the Logger uses only the factory and does not know how to instantiate a MessageDispatcher. It is also unaware of the MessageDispatcher implementation.

Recommendations

For more about Factory patterns you can check also the FactoryMethod and AbstractFactory articles.

Also if you want to level up, refresh or better understand design patterns and software design in general, I also recommend reading the following books:

A must in Design Patterns

A very well written book with plenty of examples and insights

Write A Comment

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.