C# Corner

Abstract and Delegate-based Factories in .NET

In part 2 of a series of columns on using factory patterns, Patrick Steele shows how factory patterns can be used to make your applications more flexible.

In part 1 of this series, I began exploring software factory patterns and how they can be used to enable more flexible application code. In this installment, I'll look at abstract factories and delegate-based factories and how they can be used to support customizations.

The Abstract Factory
Many times, an application that uses a factory will provide a "default" implementation of that factory. This way, you have a complete working factory that creates all of the different components you need. If one or two of those components have to be customized, you derive from the base factory and implement the customizations.

Let's revisit our application from earlier. Instead of using an interface, we'll use a base class for our factory. Also, our application's requirements have expanded and we now do some logging. The logs are stored alongside our data source (either File, FTP or in Azure), so we've pushed creation of our logger into the factory as well:

	class ProcessorFactory
	{
		public virtual ITextProcessor CreateProcessor()
		{
			return new FileProcessor();
		}

		public virtual ILogger CreateLogger()
		{
			return new FileLogger();
		}
	}

Notice that the methods are virtual. This allows customization of only certain parts of the factory. Suppose the client wants logging messages to always go to a database? We implement an ILogger that writes to a database:

	class DBLogger : ILogger
	{
		public void Log(string message)
		{
			// write message to database
		}
	}

Then create a new factory to handle the creation of the specialized logger:

	class CustomizedProcessorFactory : ProcessorFactory
	{
		public override ILogger CreateLogger()
		{
			return new DBLogger();
		}
	}

This solves the ripple-down effect we saw with interfaces. If a new component is needed and a default implementation is done in the base ProcessorFactory, all derived classes will automatically get that default implementation via inheritance. If the derived classes need customization, they override the CreateXXX method.

Customized Object Creation
The Factory Pattern is usually associated with a group of patterns called "Creational," since it deals with the creation of objects. By default, most objects are created with the "new" keyword. However, as we saw in a previous article (Inversion of Control Patterns for the Microsoft .NET Framework, August 2010), we sometimes want to change how an object gets created. A pluggable factory is one way to accomplish this.

In Microsoft's ASP.NET MVC (Model-View-Controller) framework, a "controller" (which is a C# class) handles a request that comes in from the Web server. Different controllers handle different URL's. Once the MVC framework has decided the type of controller to handle a request, it uses a factory to instantiate the controller. A default controller factory is provided in the MVC framework and looks something like this:

	class DefaultControllerFactory
	{
		public virtual IController
GetControllerInstance(Type controllerType)
		{
			return
(Controller)Activator.CreateInstance(controllerType);
		}
	}

While this is highly simplified, you can see that the creation of the controllers is done by the factory, not the MVC framework itself. This lets us easily change the way our controllers get created. Suppose you want to use the Castle Windsor IoC container to manage your controllers and their dependencies? In that case, using Activator.CreateInstance wouldn't work because you want your IoC container to handle the instantiation of the controllers.

Instead, we can subclass the DefaultControllerFactory and override the method that creates the controller -- and we can use our IoC container to handle the creation! A simple example might look something like this:

	class WindsorController : DefaultControllerFactory
	{
		private readonly IWindsorContainer container;

		public WindsorController(IWindsorContainer container)
		{
			this.container = container;
		}

		public override Controller CreateController(Type controllerType)
		{
			return container.Resolve(controllerType) as Controller;
		}

		public override void ReleaseController(Controller controller)
		{
			container.Release(controller);
		}
	}

Again, this is simplified and is for illustration purposes only. Note that we also did an override of the ReleaseController method from the default factory. This gives us an opportunity to let our IoC container know that we're done with the controller. Now we just tell the MVC framework to use this factory instead of the default one and our controllers now get resolved via our IoC container!

Delegate-Based Factories
Some developers have adopted a unique approach to factories. Instead of using a base class or interfaces, they use delegates. After all, a delegate represents a method with a particular signature. Instead of having to build separate factory classes that do little more than use the "new" keyword to create an object, delegates can be used to represent the actual factory method.

Let's see how we might use delegates to define our ProcessorFactory we introduced earlier:

	public delegate ITextProcessor CreateProcessorDelegate();
	public delegate ILogger CreateLoggerDelegate();

	class DelegateProcessorFactory
	{
		public CreateProcessorDelegate CreateProcessor { get; set; }
		public CreateLoggerDelegate CreateLogger { get; set; }

		public DelegateProcessorFactory()
		{
			this.CreateProcessor = new CreateProcessorDelegate(
delegate { return new FileProcessor(); });
			this.CreateLogger = new CreateLoggerDelegate(
delegate { return new FileLogger(); });
		}
	}

As before, this provides a default implementation that does processing and logging to files. This may seem like a bit of work to define all of those delegates. And actually, it is -- especially as the number of factory methods grows. If you use .NET Framework 3.5, lambdas can make this code much easier, as well as reduce the need to create your own delegates thanks to the framework's built-in Func:

	class LambdaProcessorFactory
	{
		public Func<ITextProcessor> CreateProcessor { get; set; }
		public Func<ILogger> CreateLogger { get; set; }

		public LambdaProcessorFactory()
		{
			this.CreateProcessor = () => new FileProcessor();
			this.CreateLogger = () => new FileLogger();
		}
	}

Customizing this factory is easy and doesn't require any subclassing. Simply assign a new lambda expression to be used in the creation of the processor and logger. Earlier, we had to create a specialized factory to use the DBLogger. With the lambda approach, this customization is much easier:

		var factory = new LambdaProcessorFactory();
		factory.CreateLogger = () => new DBLogger();

Conclusion
As you can see, the factory pattern can be a powerful way to allow your applications to be more flexible and easier to adapt to new conditions in the future. It's more common in libraries and API's, but careful use inside your own applications will lead to big benefits down the road.

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