Click here to Skip to main content
15,885,151 members
Articles / Programming Languages / C#
Article

Implementing the factory pattern using attributes and activation

Rate me:
Please Sign up or sign in to vote.
4.85/5 (37 votes)
9 Mar 20036 min read 93.5K   302   87   12
This article shows you how to implement the Factory pattern using attributes and activation.

Introduction

The basic rule for a factory pattern is that the factory should create an instance of an object with a known ancestor, and an unknown implementation behind. Which object to be created is decided by the factory based on the criteria’s passed to the factory’s method that shall instantiate the object.

I would also like to share some rules that I usually follow when I am writing a factory.

  1. If you need the factory to be able to create more classes than it was originally designed for, you should not have to modify the factory code.
  2. The factory should be equally unknowing as the caller about the implementation behind the classes that it can instantiate.
  3. The factory should provide a fixed mechanism how to decide which class to instantiate. The class itself should provide some information to the factory, which the factory should base it’s decision on.

One way solve the issues presented in rules 1 and 2 is to let the factory have an internal list of types it could instantiate, and to provide a register mechanism so that it’s possible to tell the factory which classes it can instantiate.

To solve the third rule it would require the classes which the factory can instantiate to override a static abstract method from the ancestor class, since you need  information from the class before it’s instantiated and you need to override it’s behaviour in descendent classes. This is not possible in .NET since there is no meaning by having an static abstract method in .NET, therefore we must solve this with a different approach: Attributes.

In this article I will show you how to implement a factory, which uses attributes to decide which class to instantiate. To fully understand this article you need to have some basic knowledge about attributes.

Background

Why do I think these three rules are so important?

Let's say the factory is part of a plug in architecture and could not be modified because it’s compiled into a host application or a separate DLL. A third party should easily be able to write a plug in without modifying the factory code. Even if the current situation is not requiring this at the moment. If you use these three rules when developing your system this could easily be accomplished if the requirements changes later on

I also strongly believe that a class should be an expert on itself. Since a class obviously knows about it’s own implementation, why shouldn’t the class be a part of the decision making process that decides when to use it? If the class provide the factory with information to base it’s decision on, we could state that the class is an expert on itself since it knows what it does and when to be used.

About the code

To illustrate how to build such a factory, I have decided to use the following case:

The mission is to create an ASP.NET page which says: "Hello World" when a client is requesting the page. The problem is that the page should be available to various types of clients, like HTML browsers, XML Spy and other XML based tools, mobile phones and other WML browsers. The page should also be able to be requested manually by a GET from a telnet client.

Depending on the User Agent, the page should be generated differently, either in HTML, XML, WML or in plain text for the telnet client.

To simplify the demonstration, the application will actually be an ordinary windows application with an input field where to enter the "User Agent", and a textbox to display the output.

The basic idea how to solve this task is to have an abstract class

C#
public abstract class ResponseFormatter 
{ 
    public abstract string FormatResponse(string Text); 
} 

which is inherited by several classes and implemented in different ways. For example one class is implemented to format the text using HTML and another class is formatting the text using WML etc. These classes are the classes that the factory is responsible for instantiating.

The factory class looks like this:

C#
public sealed class ResponseFormatterFactory
{
    private ArrayList RegisteredImplementations;
    public ResponseFormatterFactory()
    {
        RegisteredImplementations = new ArrayList();
    }
    
    public void RegisterResponseFormatter(Type ResponseFormatterImpl)
    {
        if (!ResponseFormatterImpl.IsSubclassOf(typeof(ResponseFormatter)))
            throw new ResponseFactoryError("Response Formatter 
                must inherit from class ResponseFormatter");

        RegisteredImplementations.Add(ResponseFormatterImpl);
    }

    public ResponseFormatter CreateResponseFormatter(string UserAgent)
    {
        ...
    }
}

To make the factory aware of the classes it can instantiate, it has an ArrayList that containins the System.Type for each ResponseFormatter implementation registered. You must therefor register all implementations with the factory using the method RegisterResponseFormatter in order to make it aware of them.  The method RegisterResponseFormatter checks that the ResponseFormatter parameter actually is a type subclassed from the abstract class ResponseFormatter.

Each class that the factory should manage should be "tagged" with one or more UserAgentAttribute which looks like this:

[AttributeUsage(AttributeTargets.Class, AllowMultiple=true)]
public class UserAgentAttribute : Attribute
{
    private string UserAgentText;
    private bool ExactMatch;

    public UserAgentAttribute(string UserAgentText, bool ExactMatch)
    {
        this.UserAgentText = UserAgentText;
        this.ExactMatch = ExactMatch;
    }

    public virtual bool MatchesUserAgent(string UserAgent)
    {
        if (!ExactMatch)
        {
            return (UserAgent.ToLower().IndexOf(UserAgentText.ToLower()) != -1);
        }
        else
            return UserAgent.ToLower().Equals(UserAgentText.ToLower());
    }
}

The AttributeUsage attribute says that this attribute is applicable to classes only, and it's okay to use several of these attributes on the same class.

When applying this attribute to a class you must supply two parameters: UserAgentText and ExactMatch. The first parameter is the string that is tested with the useragent  you supply when you invoke the factory's CreateResponseFormatter method. The next parameter defines how the match should be performed: exact match (true) or partial match (false).

The method MatchesUserAgent performes the actual test and based on those two parameters it decides whether this class could serve the the specified UserAgent.

Let's have a look at a class that implements the abstract ResponseFormatter.

C#
[UserAgent("Mozilla", false)]
public class HTMLResponseFormatter : ResponseFormatter
{
    public HTMLResponseFormatter() {}
    public override string FormatResponse(string Text)
    {
        System.Text.StringBuilder Response = new System.Text.StringBuilder();

        Response.Append("<html></body>");
        Response.Append(Text);
        Response.Append("</body></html>");

        return Response.ToString();
    }
}        

First of all, we have the attribute which says that this class should be used when the useragent contains (partial matching) the string "Mozilla".

The method FormatResponse is overidden to return the string in the variable Text formatted as HTML.

Now let's check inside the method CreateResponseFormatter in the ResponseFormatterFactory class:

C#
public ResponseFormatter CreateResponseFormatter(string UserAgent)
{
    // loop thru all registered implementations
    foreach (Type impl in RegisteredImplementations)
    {
        // get attributes for this type
        object[] attrlist = impl.GetCustomAttributes(true);

        // loop thru all attributes for this class
        foreach (object attr in attrlist)
        {
    
            if (attr is UserAgentAttribute)
            {
            // okay, we found an useragent attribute
            // lets check if it matches our useragent
                if (((UserAgentAttribute) attr).MatchesUserAgent(UserAgent))
                {
                    // okay, this ResponseFormatter could be 
                    // used with the specified UserAgent
                    // created instance and return
                    return (ResponseFormatter) 
                               System.Activator.CreateInstance(impl);
                }
            }
        }
    }

    // if we got this far, no ResponseFormatter implementation 
    // could be used with this UserAgent
    throw new ResponseFactoryError("Could not find a ResponseFormatter 
                                    implementation for this UserAgent");
}

This method consists of two nested loops: the first loop iterate through all the registered types. The second loop iterates through all attributes for the current type. If the attribute is a UserAgentAttribute it calls the attributes MatchesUserAgent method to find out if this class could serve the specified UserAgent. If the MatchesUserAgent returned true we simply instantiate the class behind the type using activation and returns it. If the loop's have finished without finding a implementation to use, an exception is raised.

Well let's put everything together and have a look where the factory is used. First we'll create an instance of the factory and register our implementation with it.

C#
// create factory 
factory = new ResponseFormatterFactory();

// register implementations
factory.RegisterResponseFormatter(typeof(HTMLResponseFormatter));
factory.RegisterResponseFormatter(typeof(XMLResponseFormatter));
factory.RegisterResponseFormatter(typeof(TextResponseFormatter));
factory.RegisterResponseFormatter(typeof(WMLResponseFormatter));

Next up is to finally use the factory:

C#
private void btnHello_Click(object sender, System.EventArgs e)
{
    try
    {
        tbResponse.Text = 
          factory.CreateResponseFormatter(
          cobxUserAgent.Text).FormatResponse("Hello World!!");
    }
    catch (ResponseFactoryError error)
    {
        MessageBox.Show(error.Message, "Error"); 
    }
}

And that's it!

Points of Interest

Since I am an old Delphi coder I am used to the fact that it's possible to utilize static abstract methods, or as it's called in Delphi: an abstract class function. When I found out that this wasn't possible in .NET I was very depressed for a couple of days, until I stumbled upon attributes! Attributes is a very powerful tool for the programmer and could in a way mimic the behaviour of abstract static methods since you can derive attributes as well.

History

  • 2003-02-24 -- Initial release
  • 2003-02-28 -- Updated

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior) Kodkultur AB
Sweden Sweden
A Pretty nice guy Smile | :)

Comments and Discussions

 
QuestionInterfaces? Pin
Horia Tudosie27-Sep-06 10:21
Horia Tudosie27-Sep-06 10:21 
Questionstatic factory.create? Pin
Wcohen5-Sep-05 3:17
Wcohen5-Sep-05 3:17 
GeneralReally very good Pin
Clickok24-May-05 11:29
Clickok24-May-05 11:29 
GeneralGood articles... Pin
Hugo Hallman6-Jun-04 21:43
Hugo Hallman6-Jun-04 21:43 
GeneralRe: Good articles... Pin
John Gunnarsson8-Jun-04 4:03
John Gunnarsson8-Jun-04 4:03 
GeneralStatic Abstract Pin
Keith Farmer29-Oct-03 9:58
Keith Farmer29-Oct-03 9:58 
GeneralRe: Static Abstract Pin
John Gunnarsson19-Sep-04 11:33
John Gunnarsson19-Sep-04 11:33 
GeneralVery nice! Pin
Richard Deeming11-Mar-03 1:07
mveRichard Deeming11-Mar-03 1:07 
GeneralRe: Very nice! Pin
John Gunnarsson15-Mar-03 6:16
John Gunnarsson15-Mar-03 6:16 
GeneralNice article mate Pin
Sijin10-Mar-03 17:41
Sijin10-Mar-03 17:41 
GeneralExcellent article Pin
sheckel10-Mar-03 10:18
sheckel10-Mar-03 10:18 
GeneralRe: Excellent article Pin
John Gunnarsson15-Mar-03 6:07
John Gunnarsson15-Mar-03 6:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.