I recently developed a toy project called Rareburg article feed generator. It basically gets the articles from Rareburg.com (a marketplace for collectors) and creates an RSS feed. One challenge I had was picking the feed formatter class (RSS vs Atom). I utilized Factory Method design pattern to solve that and this post discusses the usage of the pattern over that use case.
Use case: Creating RSS Feed Formatter
I wanted my feed generator to support both RSS and Atom feeds based on the value specified in the configuration. In order to pass the XML validations, they needed to be modified a bit and I wanted these details hidden from the client code.
What is Factory Method?
Here's the official definition from GoF:
Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.
Factory Method pattern is one of the creational patterns which make it easy to separate object creation from the actual system. The actual business logic doesn't need to care about these object creation details.
Implementation
To see the implementation at a glance, let's have a look at the class diagram:
The base abstract
class is called ArticleFeedGenerator
(it corresponds to Creator
in the GoF book). Here's a shortened version:
public abstract class ArticleFeedGenerator
{
private SyndicationFeedFormatter _feedFormatter;
public abstract SyndicationFeedFormatter CreateFeedFormatter();
public void Run()
{
var allArticles = _feedDataClient.GetAllArticles();
_feed = _feedService.GetFeed(allArticles);
_feedFormatter = CreateFeedFormatter();
_publishService.Publish(_feedFormatter);
}
}
ArticleFeedGenerator
does all the work except creating a concrete implementation of SyndicationFeedFormatter
. It delegates it to the derived class which provide the implementation for CreateFeedFormatter abstract
method.
And here come the concrete implementations (which correspond to ConcreteCreator
):
public class AtomFeedGenerator : ArticleFeedGenerator
{
public AtomFeedGenerator(
IFeedDataClient feedDataClient,
IFeedService feedService,
IPublishService publishService,
IFeedSettings feedSettings)
: base (feedDataClient, feedService, publishService, feedSettings)
{
}
public override SyndicationFeedFormatter CreateFeedFormatter()
{
return new Atom10FeedFormatter(_feed);
}
}
In order to pass RSS validations, RSS formatter needed more "love" than the Atom version above:
public class RssFeedGenerator : ArticleFeedGenerator
{
public RssFeedGenerator(
IFeedDataClient feedDataClient,
IFeedService feedService,
IPublishService publishService,
IFeedSettings feedSettings)
: base (feedDataClient, feedService, publishService, feedSettings)
{
}
public override SyndicationFeedFormatter CreateFeedFormatter()
{
var formatter = new Rss20FeedFormatter(_feed);
formatter.SerializeExtensionsAsAtom = false;
XNamespace atom = "http://www.w3.org/2005/Atom";
_feed.AttributeExtensions.Add(new XmlQualifiedName
("atom", XNamespace.Xmlns.NamespaceName), atom.NamespaceName);
_feed.ElementExtensions.Add(new XElement(atom + "link",
new XAttribute("href", _feedSettings.FeedUrl),
new XAttribute("rel", "self"),
new XAttribute("type", "application/rss+xml")));
return formatter;
}
}
Concrete creators create concrete products but the factory method returns the abstract
product which ArticleFeedGenerator
(abstract
creator) works with.
To avoid hard-coding the class name, a helper method called CreateFeedGenerator
is added. The client code calls this method to get either a AtomFeedGenerator
or a RssFeedGenerator
based on configuration value.
class Program
{
static void Main(string[] args)
{
var feedSettings = new AppConfigFeedSettings();
ArticleFeedGenerator feedGenerator = CreateFeedGenerator(feedSettings);
feedGenerator.Run();
}
private static ArticleFeedGenerator CreateFeedGenerator(IFeedSettings feedSettings)
{
string feedFormat = feedSettings.FeedFormat;
switch (feedFormat.ToLower())
{
case "atom": return new AtomFeedGenerator(new RareburgClient(),
new RareburgArticleFeedService(), new S3PublishService(), feedSettings);
case "rss": return new RssFeedGenerator(new RareburgClient(),
new RareburgArticleFeedService(), new S3PublishService(), feedSettings);
default: throw new ArgumentException("Unknown feed format");
}
}
}
Resources
CodeProject