Search code examples
c#.netvisual-studiodesign-patternsfactory-pattern

using the factory pattern with derived classes accepting diverse number of parameters


I'm using the following factory pattern:

using System;

class Program
{
    abstract class Position
    {
    public abstract string Title { get; }
    }

    class Manager : Position
    {
    public override string Title
    {
        get
        {
        return "Manager";
        }
    }
    }

    class Clerk : Position
    {
    public override string Title
    {
        get
        {
        return "Clerk";
        }
    }
    }

    class Programmer : Position
    {
    public override string Title
    {
        get
        {
        return "Programmer";
        }
    }
    }

    static class Factory
    {
    /// <summary>
    /// Decides which class to instantiate.
    /// </summary>
    public static Position Get(int id)
    {
        switch (id)
        {
        case 0:
            return new Manager();
        case 1:
        case 2:
            return new Clerk();
        case 3:
        default:
            return new Programmer();
        }
    }

The way to use this pattern is in the example from the same source:

static void Main()
{
for (int i = 0; i <= 3; i++)
{
    var position = Factory.Get(i);
    Console.WriteLine("Where id = {0}, position = {1} ", i, position.Title);
}
}

Should I be using this pattern if my derived classes are using different numbers of parameters for their constructors?

The probable modification that I would need to make is when instantiating the factory:

var position = Factory.Get(i);

I would probably need to pass in parameters for all of the derived classes, regardless of whether they would use them or not:

var position = Factory.Get(i, param1, param2, param3);

and the switch statement would need to be modified:

public static Position Get(int id, param1, param2, param3) //HERE IS THE MODIFIED PARAM LIST
{
    switch (id)
    {
    case 0:
        return new Manager(param1); //MODIFIED
    case 1:
    case 2:
        return new Clerk(param2, param3); //MODIFIED
    case 3:
    default:
        return new Programmer(param3); //MODIFIED
    }
}

Do the modifications that I've made to the factory pattern break the pattern, and should I be using a different pattern for object creation?


Solution

  • The use of a factory is to abstract the requirements of how to create an object so as to allow the client to ask for an object without needing to know the details of how.

    One of the most classic uses of the pattern is having a app create a database connection based on a key that might return a MSSQL, SQLite, MySQL, etc, connection. The client doesn't care what the implementation is so long as it supports all of the required operations.

    So the client should be completely agnostic to the parameters required.

    Here's how to do it.

    I've slightly expanded the Position classes:

    abstract class Position
    {
        public abstract string Title { get; }
    }
    
    class Manager : Position
    {
        public Manager(string department) { }
        public override string Title => "Manager";
    }
    
    class Clerk : Position
    {
        public override string Title => "Clerk";
    }
    
    class Programmer : Position
    {
        public Programmer(string language) { }
        public override string Title => "Programmer";
    }
    

    Now I've created the Factory class like this:

    static class Factory
    {
        private static Dictionary<int, Func<Position>> _registry =
            new Dictionary<int, Func<Position>>();
    
        public static void Register(int id, Func<Position> factory)
        {
            _registry[id] = factory;
        }
    
        public static Position Get(int id)
        {
            return _registry[id].Invoke();
        }
    }
    

    Then it becomes easy to use the factory. When you're initializing the application you'd write this kind of code:

    Factory.Register(1, () => new Manager("Sales"));
    Factory.Register(2, () => new Clerk());
    Factory.Register(3, () => new Programmer("C#"));
    

    Now, later, when the client code wants a Position object it just needs to do this:

    var position = Factory.Get(3);
    

    In my testing when I output position.Title I got Programmer printed to the console.