Sunday, July 25, 2010

Using the Factory Pattern as an Assembly Gateway

Sometimes its useful to tightly control access to a framework or utility assembly to ensure it is either used correctly or is easy to use.  We as developers are guilty of often building in too much flexibility, and too much always equates to more complexity.  This normally results in questions from consuming developers like:

 "How do I use this, there seems to be more than one way to do anything?"

Having one way to do anything can sometimes create a intuitive and easy to use API.  Having only one way to do certain things obviously has trade offs in flexibility, it very much depends on the use case of your utility assembly and the consequences if consumers misuse it.  My personal preference is to think about possible extensions and where functionality could be added but not necessarily expose them publically or add the extension code at all until there is a demand for them. (Demand > 1 BTW).

Its also worth considering implementing strict and few use cases for your assembly if its consumers are likely to be unit testing.  A Test Driven Developer will want to have your babies if you are exposing nothing but interfaces and all references between objects are interfaces and creation is controlled through an interfaced factory.  Not to mention that using this approach means your consumers are forced to write code that is unit testable.

Enter the Factory Method Patten.

Basically, using a factory, will create an object for you and return you a reference through an interface. You are not in control what exact concrete type is behind the interface nor should you care, as long as you get an instance with the right interface.  The factory is also interfaced so that when it comes time to test, you can use a different factory or change its configuration to return a different type. (See IoC containers).

Here's a schematic diagram showing the idea of using a factory as a gateway into a shared assembly:


Most types  within the assembly are "internal" (ie the class is declared as internal).  Each type that can be used externally are interfaced, and the interface is public.  All references to other types, refer to the other type using an interface. For example type C and type A both have a reference to type B, but instead of referencing B directly they reference an interface of B.  The Factory is responsible for creating types and returning a reference to an interface.  The factory itself also implements an interface to allow the entire Assembly to be replaced with a complete set of test Mocks.

Lets see a code example.  First lets start by looking at the end result usage:
The protected assembly in this example is the "InterfaceIntoAssembly" namespace.  


namespace ConsoleApplication1
{
    using System;
    using InterfaceIntoAssembly;

    /// <summary>
    /// A Test Console application
    /// </summary>
    public static class Program
    {
        private static IFactory factory;

        public static IFactory Factory
        {
            get
            {
                // If no factory has been supplied yet, create the factory live production
                // code will use.
                return factory ?? (factory = new Factory());
            }

            set
            {
                // Provided for testing (alternatively could be constructor argument).
                factory = value;
            }
        }

        /// <summary>
        /// Main entry point
        /// </summary>
        /// <param name="args">The command line args.</param>
        public static void Main(string[] args)
        {
            IConnection connection = Factory.CreateConnection();
            IMessage message = connection.CreateMessage();
            IMessage reply = message.CreateReply();
            connection.Send(reply);
        }
    }
}
This code is from a Console application that consumes the InterfaceIntoAssembly.DLL.  So to relate to the diagram above this code is from the "External Consumer of Assembly" green box. First thing to note is that I'm using a class scoped Factory property, this is to allow a Unit Test to create this class in test mode, and then inject a mocked Test Factory into the Factory property.  I prefer to have a property for a factory rather than pass it into the constructor of the class; although sometimes passing a factory into the constructor is necessary. You might ask why don't I just create an application wide static property and refer to the factory that way, much like using the StructureMapstatic ObjectFactory.CurrentContainer property?  When you run your tests with a multi-threaded parallel runner get back to me on the use of static object factories.

Lets look next at the Factory code:

namespace InterfaceIntoAssembly
{
    public class Factory : IFactory
    {
        public IMessage CreateMessage()
        {
            return new Message();
        }

        public IConnection CreateConnection()
        {
            return new Connection();
        }
    }
}
There are many ways to implement this, and this is the most basic.  For more flexibility reach straight for StructureMap.  
The methods available here are the Types that you want external consumers to be able to create. So if a consuming developer needs to be able to "new-up" a class you will instead of making that class public create a method here to create it.  Types that are only created internally and references are passed out of the assembly then you will not need a create method.

Finally lets take a look at the connection object to round out the example.


namespace InterfaceIntoAssembly
{
    using System;

    public interface IConnection
    {
        int Status { get; }

        IMessage Receive();

        void Send(IMessage message);

        IMessage CreateMessage();
    }

    internal class Connection : IConnection
    {
        public int Status { get; private set; }

        public IMessage Receive()
        {
            throw new NotImplementedException();
        }

        public void Send(IMessage message)
        {
            throw new NotImplementedException();
        }

        public void HiddenMember()
        {
            throw new NotImplementedException();
        }

        public IMessage CreateMessage()
        {
            throw new NotImplementedException();
        }
    }
}

Note that all references to other classes are to interfaces, or alternatively you could use abstract class if that suits better (but remember not to put code in it that will hinder different usages like testing etc).
Also note that "HiddenMember" does not appear externally outside the assembly. So from the consuming console application described above you cannot see this member.  This method perhaps should be declared as internal for clarity. All interface members must be declared as public.

I'm not using a IoC framework in this example, but you certainly can.  However my personal preference for framework or base level assemblies to avoid excessive dependencies unless there's a strong reason to include them.  This simple factory definitely does the job in this case.
I'm also intending on posting another example of consuming a third party DLL that does not expose interfaces, and how to write unit testable code against such a dependency.

When does this not work well?
  • When your framework/utility assembly has types you expect consumers to inherit from.
  • When you cannot anticipate all use cases of your framework assembly.
  • When your target audience is very senior level developers who know exactly what they are doing and want unforeseen and creative ways to extend your assembly. (In this case you'd still interface and probably just ditch the factory gateway).
When does it work well?
  • When the consequences of misusing your assembly are severe (memory leaks, financial loss etc).
  • When you are trying to make it very easy for consuming developers to use your API.
  • When you know your use cases up front and can reasonably anticipate possible extensions to your DLL.
  • When you are making extensive use of unit testing.

Note to self, when you read this again and have forgotten where the source code is, you can find it here.

No comments:

Post a Comment