简体   繁体   中英

Factory Pattern without a Switch or If/Then

I'm looking for a simple example of how to implement a factory class, but without the use of a Switch or an If-Then statement. All the examples I can find use one. For example, how could one modify this simple example (below) so that the actual factory does not depend on the Switch? It seems to me that this example violates the Open/Close principle. I'd like to be able to add concrete classes ('Manager', 'Clerk', 'Programmer', etc) without having to modify the factory class.

Thanks!

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
    {
        public static Position Get(int id)
        {
            switch (id)
            {
                case 0: return new Manager();
                case 1: return new Clerk();
                case 2: return new Programmer();
                default: return new Programmer();
            }
        }
    }

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

UPDATE:

Wow! Thanks everyone! I have learned a ton. After revewing all the feedback, I blended a few of the answers and came up with this. I'd be open to further dialog about a better way to do this.

class Program
{

    public interface IPosition
    {
        string Title { get; }
    }

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

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

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

static class PositionFactory
{
    public static T Create<T>() where T : IPosition, new()
    {
        return new T();
    }
}


static void Main(string[] args)
    {

        IPosition position0 = PositionFactory.Create<Manager>();
        Console.WriteLine("0: " + position0.Title);

        IPosition position1 = PositionFactory.Create<Clerk>();
        Console.WriteLine("1: " + position1.Title);

        IPosition position2 = PositionFactory.Create<Programmer>();
        Console.WriteLine("1: " + position2.Title);

        Console.ReadLine();
    }
}

Another Edit:

It's also possible to create an instance of the Interface using an unknown type:

static class PositionFactory
{
   public static IPosition Create(string positionName)
    {       
        Type type = Type.GetType(positionName);
        return (IPosition)Activator.CreateInstance(type);
    }
}

Which could then be called as follows:

IPosition position = PositionFactory.Create("Manager");
Console.WriteLine(position.Title);

How about this (no Dictionary required and note that you will get an syntax error if your try to Create<Position>() ):

EDIT - Updated to use an IPosition interface implemented explicitly. Only instances of IPosition can access the member functions (eg <implementation of Manager>.Title will not compile).

EDIT #2 Factory.Create should return an IPosition not T when using the interface properly.

using System;
using System.Collections.Generic;

class Program
{
    interface IPosition
    {
        string Title { get; }
        bool RequestVacation();
    }

    class Manager : IPosition
    {
         string IPosition.Title
        {
            get { return "Manager"; }
        }

        bool IPosition.RequestVacation()
        {
            return true;
        }
    }

    class Clerk : IPosition
    {
        int m_VacationDaysRemaining = 1;

        string IPosition.Title
        {
            get { return "Clerk"; }
        }

        bool IPosition.RequestVacation()
        {
            if (m_VacationDaysRemaining <= 0)
            {
                return false;
            }
            else
            {
                m_VacationDaysRemaining--;
                return true;
            }
        }
    }

    class Programmer : IPosition
    {
        string IPosition.Title
        {
            get { return "Programmer"; }
        }

        bool IPosition.RequestVacation()
        {
            return false;
        }
    }

    static class Factory
    {
        public static IPosition Create<T>() where T : IPosition, new ()
        {
            return new T();
        }
    }

    static void Main(string[] args)
    {
        List<IPosition> positions = new List<IPosition>(3);
        positions.Add(Factory.Create<Manager>());
        positions.Add(Factory.Create<Clerk>());
        positions.Add(Factory.Create<Programmer>());

        foreach (IPosition p in positions) { Console.WriteLine(p.Title);  }
        Console.WriteLine();

        Random rnd = new Random(0);
        for (int i = 0; i < 10; i++)
        {
            int index = rnd.Next(3);
            Console.WriteLine("Title: {0}, Request Granted: {1}", positions[index].Title, positions[index].RequestVacation());
        }

        Console.ReadLine();
    }
}

You could make use of custom attributes and reflection.

[PositionType(1)]
class Manager : Position
{
    public override string Title
    {
        get
        { return "Manager"; }
    }
}

[PositionType(2)]
class Clerk : Position
{
    public override string Title
    {
        get
        { return "Clerk"; }
    }
}

In your factory you could then get all classes that inherit from Position and find the one that has the PositionType attribute with the correct value.

static class Factory
{
    public static Position Get(int id)
    {
        var types = typeof(Position).Assembly.GetTypes()
            .Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(Position)))
            .ToList();

        Position position = null;
        foreach(var type in types)
        {
           type.GetCustomAttributes<PositionTypeAttribute>();

           if(type.PositionId == id)
           {
               position = Activator.CreateInstance(type) as Position;
               break;
           }
        }

        if(position == null)
        {
            var message = $"Could not find a Position to create for id {id}.";
            throw new NotSupportedException(message);
        }

        return position;
    }
}
public class PositionFactory
{
    private Dictionary<int, Type> _positions;

    public PositionFactory()
    {
        _positions = new Dictionary<int, Type>();
    }

    public void RegisterPosition<PositionType>(int id) where PositionType : Position
    {
        _positions.Add(id, typeof(PositionType));
    }

    public Position Get(int id)
    {
        return (Position) Activator.CreateInstance(_positions[id]);
    }
}

Used like this:

            var factory = new PositionFactory();
            factory.RegisterPosition<Manager>(0);
            factory.RegisterPosition<Clerk>(1);

            Position p = factory.Get(0); //Returns a new Manager instance

Why overcomplicate things? Here is one simple solution:

using System;
using System.Collections.Generic;

class Program
{
    interface IPosition
    {
        string Title { get; }
    }

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

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

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

    class Factory
    {
        private List<IPosition> positions = new List<IPosition>();
        public Factory()
        {
            positions.Add(new Manager());
            positions.Add(new Clerk());
            positions.Add(new Programmer());
            positions.Add(new Programmer());
        }

        public IPosition GetPositions(int id)
        {
            return positions[id];
        }
    }

    static void Main(string[] args)
    {
        Factory factory = new Factory();

        for (int i = 0; i <= 2; i++)
        {
            var position = factory.GetPositions(i);
            Console.WriteLine("Where id = {0}, position = {1} ", i, position.Title);
        }
        Console.ReadLine();
    }
}

Here is how to do this without using factory class at all:

using System;
using System.Collections.Generic;

class Program
{
    interface IPosition
    {
        string Title { get; }
    }

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

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

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

    static void Main(string[] args)
    {
        List<IPosition> positions = new List<IPosition> { new Manager(), new Clerk(), new Programmer(), new Programmer() };

        for (int i = 0; i <= 2; i++)
        {
            var position = positions[i];
            Console.WriteLine("Where id = {0}, position = {1} ", i, position.Title);
        }
        Console.ReadLine();
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM