- By Emmanuel Valverde
- ·
- Posted 22 Mar 2024
Intermediate Refactoring katas
Our recommendation is that you first go through Object-Oriented Programming katas so that you can move on to more complex testing concepts. If so, we..
In this post I will walk through how to refactor a Factory, moving from a sequence of if
s to a dictionary implementation, and using delegates as a type alias for my object creation methods.
Here is a naive implementation of the Factory. You can see the series of if
statements, and the direct construction and returning of the created object from the body of each:
public class MessageFactory : IMessageFactory
{
private readonly IArtifactApi _artifactApi;
private readonly ITarballReader _tarballReader;
public MessageFactory(IArtifactApi artifactApi, ITarballReader tarballReader)
{
_artifactApi = artifactApi;
_tarballReader = tarballReader;
}
public IMessage Create(string id, DateTime timestamp, string fileType, Payload payload)
{
if (fileType == FileTypes.AnalyticInstrumentsXml)
{
return new XmlMessage(_artifactApi, id, timestamp, payload);
}
if (fileType == FileTypes.BrokersXml)
{
return new XmlMessage(_artifactApi, id, timestamp, payload);
}
if (fileType == FileTypes.EconomyXml)
{
return new XmlMessage(_artifactApi, id, timestamp, payload);
}
if (fileType == FileTypes.OrdersXml)
{
return new XmlMessage(_artifactApi, id, timestamp, payload);
}
if (fileType == FileTypes.UsersXml)
{
return new XmlMessage(_artifactApi, id, timestamp, payload);
}
if (fileType == FileTypes.TradeTarball)
{
return new TarballMessage(_artifactApi, _tarballReader, id, timestamp, payload);
}
throw new NotImplementedException();
}
}
I want to separate my conditional logic from my object construction, so I Extract Method for each of the constructor invocations:
public class MessageFactory : IMessageFactory
{
private readonly IArtifactApi _artifactApi;
private readonly ITarballReader _tarballReader;
public MessageFactory(IArtifactApi artifactApi, ITarballReader tarballReader)
{
_artifactApi = artifactApi;
_tarballReader = tarballReader;
}
public IMessage Create(string id, DateTime timestamp, string fileType, Payload payload)
{
if (fileType == FileTypes.AnalyticInstrumentsXml)
{
return CreateXmlMessage(_artifactApi, id, timestamp, payload);
}
if (fileType == FileTypes.BrokersXml)
{
return CreateXmlMessage(_artifactApi, id, timestamp, payload);
}
if (fileType == FileTypes.EconomyXml)
{
return CreateXmlMessage(_artifactApi, id, timestamp, payload);
}
if (fileType == FileTypes.OrdersXml)
{
return CreateXmlMessage(_artifactApi, id, timestamp, payload);
}
if (fileType == FileTypes.UsersXml)
{
return CreateXmlMessage(_artifactApi, id, timestamp, payload);
}
if (fileType == FileTypes.TradeTarball)
{
return CreateTarballMessage(_artifactApi, _tarballReader, id, timestamp, payload);
}
throw new NotImplementedException();
}
private static IMessage CreateXmlMessage(IArtifactApi artifactApi, string id, DateTime timestamp, Payload payload)
{
return new XmlMessage(artifactApi, id, timestamp, payload);
}
private static IMessage CreateTarballMessage(IArtifactApi artifactApi, ITarballReader tarballReader, string id, DateTime timestamp, Payload payload)
{
return new TarballMessage(artifactApi, tarballReader, id, timestamp, payload);
}
}
I want to convert my chain of conditionals into a Dictionary, but to do so, I need my methods to have the same type signature. I add a dummy parameter to CreateXmlMessage
:
public class MessageFactory : IMessageFactory
{
private readonly IArtifactApi _artifactApi;
private readonly ITarballReader _tarballReader;
public MessageFactory(IArtifactApi artifactApi, ITarballReader tarballReader)
{
_artifactApi = artifactApi;
_tarballReader = tarballReader;
}
public IMessage Create(string id, DateTime timestamp, string fileType, Payload payload)
{
if (fileType == FileTypes.AnalyticInstrumentsXml)
{
return CreateXmlMessage(_artifactApi, _tarballReader, id, timestamp, payload);
}
if (fileType == FileTypes.BrokersXml)
{
return CreateXmlMessage(_artifactApi, _tarballReader, id, timestamp, payload);
}
if (fileType == FileTypes.EconomyXml)
{
return CreateXmlMessage(_artifactApi, _tarballReader, id, timestamp, payload);
}
if (fileType == FileTypes.OrdersXml)
{
return CreateXmlMessage(_artifactApi, _tarballReader, id, timestamp, payload);
}
if (fileType == FileTypes.UsersXml)
{
return CreateXmlMessage(_artifactApi, _tarballReader, id, timestamp, payload);
}
if (fileType == FileTypes.TradeTarball)
{
return CreateTarballMessage(_artifactApi, _tarballReader, id, timestamp, payload);
}
throw new NotImplementedException();
}
private static IMessage CreateXmlMessage(IArtifactApi artifactApi, ITarballReader tarballReader, string id, DateTime timestamp, Payload payload)
{
return new XmlMessage(artifactApi, id, timestamp, payload);
}
private static IMessage CreateTarballMessage(IArtifactApi artifactApi, ITarballReader tarballReader, string id, DateTime timestamp, Payload payload)
{
return new TarballMessage(artifactApi, tarballReader, id, timestamp, payload);
}
}
At this point, I can create a Dictionary to map from fileType
to my creation methods:
public class MessageFactory : IMessageFactory
{
private readonly IArtifactApi _artifactApi;
private readonly ITarballReader _tarballReader;
private readonly Dictionary<string, Func<IArtifactApi, ITarballReader, string, DateTime, Payload, IMessage>> _factories =
new Dictionary<string, Func<IArtifactApi, ITarballReader, string, DateTime, Payload, IMessage>>
{
{FileTypes.AnalyticInstrumentsXml, CreateXmlMessage},
{FileTypes.BrokersXml, CreateXmlMessage},
{FileTypes.EconomyXml, CreateXmlMessage},
{FileTypes.OrdersXml, CreateXmlMessage},
{FileTypes.UsersXml, CreateXmlMessage},
{FileTypes.TradeTarball, CreateTarballMessage}
};
public MessageFactory(IArtifactApi artifactApi, ITarballReader tarballReader)
{
_artifactApi = artifactApi;
_tarballReader = tarballReader;
}
public IMessage Create(string id, DateTime timestamp, string fileType, Payload payload)
{
return _factories[fileType](_artifactApi, _tarballReader, id, timestamp, payload);
}
private static IMessage CreateXmlMessage(IArtifactApi artifactApi, ITarballReader tarballReader, string id, DateTime timestamp, Payload payload)
{
return new XmlMessage(artifactApi, id, timestamp, payload);
}
private static IMessage CreateTarballMessage(IArtifactApi artifactApi, ITarballReader tarballReader, string id, DateTime timestamp, Payload payload)
{
return new TarballMessage(artifactApi, tarballReader, id, timestamp, payload);
}
}
This is looking nicer, except for the type signature of the Dictionary, which is fairly gruesome:
private readonly Dictionary<string, Func<IArtifactApi, ITarballReader, string, DateTime, Payload, IMessage>> _factories =
new Dictionary<string, Func<IArtifactApi, ITarballReader, string, DateTime, Payload, IMessage>> {/*...*/};
Now, later in my refactoring I would want to remove the primitive obsession around fileType
by using either an enum or, preferably, a class with behaviour. But at the moment I’m focusing on internally refactoring this Factory.
I would also like to remove the data clumps passed into the creation functions. Again, I might find an appropriate type to contain the data, and this might lead me to a more polymorphous implementation.
But right now, I want to make my code a little more succinct and semantically clearer, so I use a delegate as a type alias for Func<IArtifactApi, ITarballReader, string, DateTime, Payload, IMessage>
:
public class MessageFactory : IMessageFactory
{
private readonly IArtifactApi _artifactApi;
private readonly ITarballReader _tarballReader;
private readonly Dictionary<string, CreateMessage> _factories =
new Dictionary<string, CreateMessage>
{
{FileTypes.AnalyticInstrumentsXml, CreateXmlMessage},
{FileTypes.BrokersXml, CreateXmlMessage},
{FileTypes.EconomyXml, CreateXmlMessage},
{FileTypes.OrdersXml, CreateXmlMessage},
{FileTypes.UsersXml, CreateXmlMessage},
{FileTypes.TradeTarball, CreateTarballMessage}
};
public MessageFactory(IArtifactApi artifactApi, ITarballReader tarballReader)
{
_artifactApi = artifactApi;
_tarballReader = tarballReader;
}
public IMessage Create(string id, DateTime timestamp, string fileType, Payload payload)
{
return _factories[fileType](_artifactApi, _tarballReader, id, timestamp, payload);
}
private static IMessage CreateXmlMessage(IArtifactApi artifactApi, ITarballReader tarballReader, string id, DateTime timestamp, Payload payload)
{
return new XmlMessage(artifactApi, id, timestamp, payload);
}
private static IMessage CreateTarballMessage(IArtifactApi artifactApi, ITarballReader tarballReader, string id, DateTime timestamp, Payload payload)
{
return new TarballMessage(artifactApi, tarballReader, id, timestamp, payload);
}
private delegate IMessage CreateMessage(IArtifactApi artifactApi, ITarballReader tarballReader, string id,DateTime timestamp, Payload payload);
}
It is interesting that neither of the methods explicitly implements this delegate: it’s sufficient for the signatures to coincide. This is one of those areas of C# where functional programming patterns creep into an otherwise fairly object-oriented idiom (type aliases or abbreviations are commonplace in F#, for example). I would be wary of exposing this delegate outside the class in which it is declared, and would choose a more object-oriented approach if I wanted to extract different creation behaviours, but I like the succinctness and clarity that this technique gives me within a single class.
Image credit: Venetian Carnival Mask by gnuckx is licensed under CC0 1.0 / cropped and resized from original
Our recommendation is that you first go through Object-Oriented Programming katas so that you can move on to more complex testing concepts. If so, we..
What is database refactoring for? What are the risks involved and how can we avoid them? Is it really beneficial to our systems? Cameron Raw,..
Sometimes, rewriting and modernising code just isn't an option. When this happens, refactoring is one of the best options.
Join our newsletter for expert tips and inspirational case studies
Join our newsletter for expert tips and inspirational case studies