The code is available on GitHub here
The FactoryMethod pattern enables us to encapsulate object creation and let other subclasses decide what object to create. This can have multiple benefits. First, we decouple the client from the actual object creation which helps with the maintainability of the application (otherwise every time the creation of the object is changed, then all the code that are specifically creating the object need also to be changed). In addition, many parts of the application can now use the object through the factory, keeping the creation process separated from the rest of the application, without knowing exactly how the object is created. Furthermore, we achieve extensibility because in the future we can extend the factory class to create a new implementation that returns a new product without changing the rest of the application.
Main difference with the simple factory
The main difference with the simple factory is that the create method is abstract. This makes the factory unaware and decoupled of the concrete objects that are returned in the create method. This allows us to inject different factory implementations creating different subclasses of the returned object.
The MessageDispacher – Logger example
This example is first developed in Simple/Static Factory article. This time we expand it further and implement the Factory Method creating a new MessageDispatcherFactory.
Suppose we have a Logger that writes messages either to console or to to disk depending on the environment the application is running. We want the client (the Logger in this case) be unaware of where and how the message will be written. For this purpose we create a new MessageDispatcher class that will be the product the factory creates.
Using the FactoryMethod we can create a MessageDispatcherFactory class that thought its subclasses instantiates a MessageDispatcher and returns it to the client. The MessageDispatcher itself is abstract helping with the extensibility and decoupling inside the application. Also, the MessageDispatcherFactory will have an abstract method Create(..) that lets the subclasses to handle which MessageDispatcher will be instantiated. The factory’s code is shown below:
public abstract class MessageDispatcherFactory
{
public MessageDispatcher Create(string message)
{
MessageDispatcher dispatcher = this.CreateDispatcher();
dispatcher.SetMessage(message);
dispatcher.PrepareMessage();
return dispatcher;
}
protected abstract MessageDispatcher CreateDispatcher();
}
In the example above, we also see two more methods, the SetMessage(..) and PrepareMessage(). These methods are needed in order to properly create a MessageDispatcher. All methods that are necessary and common across all the MessageDispatcher subclasses initialization, should be put in the abstract factory class.
The MessageDispatcher product
Next, we have two implementations of the MessageDispatcher (the product). One is the ConsoleMessageDispatcher that removes all excess spaces when preparing the message, and the other is the FileSystemMessageDispatcher which writes the message to disk as is.
public abstract class MessageDispatcher
{
protected string Message;
public abstract string Description { get; }
public abstract void Dispatch();
public abstract void SetMessage(string message);
public abstract void PrepareMessage();
}
public class ConsoleMessageDispatcher : MessageDispatcher
{
public override string Description => "I am a console message dispatcher.";
public override void Dispatch()
{
Console.WriteLine(this.Message);
}
public override void SetMessage(string message)
{
this.Message = message;
}
public override void PrepareMessage()
{
this.Message = Regex.Replace(this.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(this.path, this.Message);
}
public override void SetMessage(string message)
{
this.Message = message;
}
public override void PrepareMessage() { }
}
MessageDispatcherFactory implementations
Likewise, the MessageDispatcherFactory will also have two implementations, each of them returning one of the MessageDispatcher implementations. Also notice that each factory subclass implements the create method differently and return a different concrete type of the MessageDispatcher.
public class ConsoleMessageDispatcherFactory : MessageDispatcherFactory
{
protected override MessageDispatcher CreateDispatcher()
{
return new ConsoleMessageDispatcher();
}
}
public class FileSystemMessageDispatcherFactory : MessageDispatcherFactory
{
protected override MessageDispatcher CreateDispatcher()
{
string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "logs.txt");
return new FileSystemMessageDispatcher(path);
}
}
Now, every client that needs an implementation of a MessageDispatcher can use the MessageDispatcherFactory as a constructor injection.
How LoggerClient can use the factory
Finally, the LoggerClient has reference only to the MessageDispatcherFactory and MessageDispacher and is unaware and decoupled of the concrete types that are hidden behind the abstractions. That means we can have more implementations in the future without changing the LoggerClient, MessageDispatcherFactory or MessageDispacher classes. We can inject the factory to the LoggerClient constructor as shown below:
public class LoggerClient
{
private readonly MessageDispatcherFactory _factory;
public LoggerClient(MessageDispatcherFactory factory)
{
_factory = factory;
}
public void Log(string message)
{
var messageDispatcher = this._factory.Create(message);
messageDispatcher.Dispatch();
}
}
A usage example of the LoggerClient is shown below:
static void Main(string[] args)
{
var consoleLogger = new LoggerClient(new ConsoleMessageDispatcherFactory());
var fileSystemLogger = new LoggerClient(new FileSystemMessageDispatcherFactory());
consoleLogger.Log("a message for console dispatcher.");
fileSystemLogger.Log("a message for file system dispatcher.");
}
Recommendations
For more about Factory patterns you can check also the SimpleFactory 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