C# Corner

The Factory Pattern in .NET

Just as a car factory creates cars on an as-needed basis, we can create "factories" in our code to create objects for our applications' specific needs. In this first article of a series, we'll explain what the factory pattern is and how you can use it in your code to make your applications more flexible.

Just as a car factory creates cars on an as-needed basis, we can create "factories" in our code to create objects for our applications' specific needs. In this article, we'll explain what the factory pattern is and how you can use it in your code to make your applications more flexible.

Object Creation
One of the fundamental actions our applications do is create objects. Sure, there are a ton of objects that we use as part of a framework or a library, but we do a lot of object creation:

		var widget = new Widget();

As our applications grow and business needs change, we often find ourselves defining an interface for our objects or perhaps a base class of functionality that we want derived classes to start with. As this complexity grows, we sometimes end up writing code that becomes difficult to maintain. When we have more and more objects to create, we start setting flags to control which class will be instantiated and used.

Let's look at a simple example. We have an application that does some processing of text that it reads in. After processing, it writes the processed text back out to disk. We've created an interface for processing the text:

	interface ITextProcessor
{
string ReadText();
void SaveText(string processedText);
}

Right now, our client is storing everything on disk. So we create a file-based ITextProcessor:

	class FileProcessor : ITextProcessor
{
public string ReadText()
{
// read in text from a
// file and return it
return null;
}

public void SaveText(string processedText)
{
// write processedText out to file.
}
}

Very simple and clean. Creating an ITextProcessor for a file is accomplished with:

ITextProcessor processor = new FileProcessor();

After some amount of time, the client wants to switch to an FTP site as the source of their data. Not a huge problem. We've programmed against an interface, so we just need a new implementation of ITextProcessor that works against an FTP site:

	class FTPSiteProcessor : ITextProcessor
{
public string ReadText()
{
// connect to FTP site
// download file contents
// return file contents as a string
return null;
}

public void SaveText(string processedText)
{
// create a file of the processedText
// connect to FTP site
// upload the file.
}
}

Now we need all instances of ITextProcessor to be created as an FTPSiteProcessor. In the interest of time, we do a search/replace across the entire codebase to change all instances, where we created a FileProcessor to now create an FTPSiteProcessor:

	ITextProcessor processor = new FTPSiteProcessor();

Not too bad -- until the client decides that, for redundancy, they've moved all of their data up "in the cloud" on an Azure service. Again, not a huge hurdle, except for the fact that the client's European division is not sold on the "cloud storage" model and wants to continue to use FTP!

Now we need to create a different ITextProcessor based on some configuration option. Either we'll use a compiler directive and compile different versions for the US and European division:

#if US
ITextProcessor processor = new FTPSiteProcessor();
#else
ITextProcessor processor = new AzureProcessor();
#endif

Whoa. That is ugly! We decide we want to maintain a single executable, but we'll set a configuration option to determine which reader to use:

	ITextProcessor processor;
if (ConfigurationManager.AppSettings["division"] == "US")
{
processor = new AzureProcessor();
}
else
{
processor = new FTPSiteProcessor();
}

But this is just getting messy. Imagine another division that wants to fall back to the FileProcessor? To clean this up, we'll build some factories!

Our TextProcessor Factories
First thing we'll do is create an interface for our factory. Just like other components in our architecture, an interface gives us the ability to swap out implementations -- and it will make unit testing easier as we can plug in a stubbed factory:

	interface ITextProcessorFactory
{
ITextProcessor CreateProcessor();
}

Now we implement the various types of factories to create our different types of ITextProcessors:

	class FileProcessorFactory : ITextProcessorFactory
{
public ITextProcessor CreateProcessor()
{
return new FileProcessor();
}
}

class FTPSiteProcessorFactory : ITextProcessorFactory
{
public ITextProcessor CreateReader()
{
return new FTPSiteProcessor();
}
}

class AzureProcessorFactory : ITextProcessorFactory
{
public ITextProcessor CreateReader()
{
return new AzureProcessor();
}
}

The client code now only needs a reference to a global ITextProcessorFactory. You could expose this factory using the singleton pattern, but that can make testing more difficult. My recommendation would be to use an Inversion of Control container that would inject the proper ITextProcessorFactory into your application (either FileProcessorFactory, FTPSiteProcessorFactory or AzureProcessorFactory). Whichever method you decide to use, the creation of the text reader is now simply:

	void Process(ITextProcessorFactory readerFactory)
{
ITextProcessor reader = readerFactory.CreateReader();
// ...
}

We now have a single component that handles the creation of our different ITextProcessors. You've decoupled your application from the details of creating an ITextProcessor. If any of those processors have special constraints on creation, we can embed that inside the individual factory -- our application code doesn't need to know about those constraints. This leads to better decoupling of your application's components.

One of the downsides to the interface approach to factories is the ripple effect of changes. If we require a new component that is specific to our storage methodology, we'll need to add another "Create" method to our ITextProcessorFactory interface. As soon as we do that, our code won't compile until we implement (or stub) the new method in all of our other factories. The Abstract Factory can help alleviate this issue.

Next week I will continue my exploration of software factory patterns in .NET, including a dive into the Abstract Factory and Delegate-based Factories.

About the Author

Patrick Steele is a senior .NET developer with Billhighway in Troy, Mich. A recognized expert on the Microsoft .NET Framework, he’s a former Microsoft MVP award winner and a presenter at conferences and user group meetings.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube