没有开关或 If/Then 的工厂模式

Posted

技术标签:

【中文标题】没有开关或 If/Then 的工厂模式【英文标题】:Factory Pattern without a Switch or If/Then 【发布时间】:2016-02-25 23:35:09 【问题描述】:

我正在寻找一个如何实现工厂类的简单示例,但没有使用 Switch 或 If-Then 语句。我能找到的所有示例都使用一个。例如,如何修改这个简单的示例(如下)以使实际工厂不依赖于 Switch?在我看来,这个例子违反了打开/关闭原则。我希望能够添加具体类(“经理”、“职员”、“程序员”等),而无需修改工厂类。

谢谢!

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();
    

更新:

哇!感谢大家!我学到了很多。在回顾了所有的反馈之后,我混合了一些答案并想出了这个。我愿意就更好的方法进行进一步对话。

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();
    

另一个编辑:

也可以使用未知类型创建接口的实例:

static class PositionFactory

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

然后可以如下调用:

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

【问题讨论】:

你可以看看Abstract Factory Pattern 并使用依赖注入来传递正确的工厂来完成这项工作。 我会推荐 Ninject 或 Autofac 之类的东西 这是一个典型的依赖注入案例。任何 IoC 容器(Unity、Ninject 等)最基本的用途就是将其用作美化工厂。 @Adimeus ...我希望看到一个使用依赖注入和 IoC 的示例。可以请你提供一份吗? @CaseyCrookston - 我更新了我的答案,以便它根据您在其他 cmets 中的请求使用接口。 【参考方案1】:

这个怎么样(不需要字典,注意如果你尝试Create&lt;Position&gt;(),你会得到一个语法错误):

EDIT - 更新为使用显式实现的 IPosition 接口。只有 IPosition 的实例可以访问成员函数(例如,&lt;implementation of Manager&gt;.Title 不会编译)。

EDIT #2 正确使用接口时,Factory.Create 应该返回 IPosition 而不是 T。

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();
    

【讨论】:

我非常喜欢这种简单性。但是当我尝试它时,我得到一个空白控制台。让我稍微消化一下。 那里没有输出仓位内容的代码。只需添加一个 foreach(Position p in position)... 如何将 id 编号映射到 new 实例?我看到您可以按索引从positions 中提取一个实例,大致对应于 id,但是如果您需要多个 单独的 `Manager' 实例怎么办? 如果我可以直接打电话给Factory.Create&lt;Manager&gt;(),我为什么不直接打电话给new Manager()呢?工厂模式的目标是减少组件之间的耦合。 @Bradley Uffner:你问,“我为什么不直接调用 new Manager() 呢?”我可能在这里忽略了一些东西,但是调用 new Manager() 是 OP 中的示例所做的。当然,这个例子过于简单化了。在现实世界的场景中,抽象类(或接口)位置会涉及更多。使用 Clay 在这里提供的答案,我可以这样做: Position position0 = Factory.Create();现在我正在创建一个经理独有的 Position 实例。【参考方案2】:

您可以使用自定义属性和反射。

[PositionType(1)]
class Manager : Position

    public override string Title
    
        get
         return "Manager"; 
    


[PositionType(2)]
class Clerk : Position

    public override string Title
    
        get
         return "Clerk"; 
    

然后,您可以在您的工厂中获取所有继承自 Position 的类,并找到具有正确值的 PositionType 属性的类。

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;
    

【讨论】:

好的,我可以看到这个工作。但是反思似乎有点过头了?我喜欢 Adimeus 上面的建议,即使用抽象工厂模式并使用依赖注入来传递正确的工厂来完成工作。我只是还不确定如何做到这一点。 @CaseyCrookston,我想这取决于你对矫枉过正的定义。我刚刚查看了抽象模式工厂实现的 UML 图和示例代码,看起来你最终会得到更多(可以说)更难维护的代码。 我们在消息处理中使用与此类似的方法。我们有可以处理许多事件类型的事件处理程序。他们宣传他们可以使用属性处理的内容。我认为“反思成本”被夸大了。我已经完成了基准测试,并且反射确实不像“常识”所表明的那样昂贵。此外,鉴于类型的数量基本上是固定的(您需要部署一个新程序集来更改它),您可以在系统启动时缓存所有这些信息,并且在重新启动之前不需要再次加载它(例如在重新部署之后)。 反射并不慢,如果你缓存结果并且只在没有任何内容时使用它。反射的问题更多在于你只会遇到运行时错误。 @Adimeus 当您将东西编译成委托时,性能方面并没有太多需要担心的缺点。 @CSharpie,你对运行时错误是完全正确的。当使用类型动态创建东西时,您的代码中肯定需要更多的保护条件。我提供的代码在这方面应该是相当有弹性的(主要是因为它只使用它自己发现的类型)但是我过去写过一些基于反射的代码,这些代码依赖于存储在数据库中的字符串。如果你不小心,这些东西可能会非常脆弱。【参考方案3】:
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]);
    

这样使用:

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

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

【讨论】:

好的,谢谢!我已经盯着它看了一段时间,以确保我理解正在发生的一切。问题:这是否符合抽象工厂模式的要求? 不,这只是常规工厂模式的一个简单示例。如果你有多个PositionFactory 的实现,它可以用作抽象工厂的一部分。抽象工厂只是工厂的工厂,因此您可以使用与此非常相似的模式来创建抽象工厂。事实上,您可以将两个答案都作为PositionFactory 的2 个不同实现发布,因为它们使用几乎 相同的接口,并且有一个不同的工厂返回任一类型的PositionFactory OP 和这个例子的工作方式不同。 OP 为每个PositionType 预先确定了一个代码,此示例允许任何代码具有任何位置 True Sten,但我并不在乎。我只是在寻找创建任何位置实例的最佳方法,而不必在工厂类中将它们全部列出。 我会用接口更新我的答案。这也将允许我演示即使一个 Create() 它仍然是解耦的。【参考方案4】:

为什么要把事情复杂化? 这是一个简单的解决方案:

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();
    

以下是完全不使用工厂类的方法:

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();
    

【讨论】:

这需要在添加新位置时修改工厂类。它也只能分发每个位置的一个实例。 好吧,那么您只需将添加位置移到工厂类之外。它们确实需要添加到某处......在这种情况下,您甚至不需要工厂类。使用列表就可以了 除非它实际上是 实例化它返回的东西,否则我不会将其视为工厂。这更像Service Locator Pattern,只是没有“服务” 这与 Clay Ver Valen 的建议非常接近。 很公平。在那种情况下,布拉德利提供的字典答案是我见过的在类似情况下使用最多的答案。

以上是关于没有开关或 If/Then 的工厂模式的主要内容,如果未能解决你的问题,请参考以下文章

怎么进入夏普LCD工厂模式

怎样使显示器恢复出厂设置?

设计模式——工厂方法模式

设计模式之工厂方法模式

如何在工厂方法模式和抽象工厂模式之间进行选择

Java设计模式—工厂模式