【问题标题】:C#: switch vs method overload in factory classC#:工厂类中的开关与方法重载
【发布时间】:2015-11-05 12:50:15
【问题描述】:

给定类:

enum ThingEnum { A,B,C}

interface IThing { }

class A : IThing { }

class B : IThing { }

class C: IThing { }

我脑子里有两个IThingFactory 的实现。一个使用switch

class ThingFactory
{
    public IThing MakeThing(ThingEnum type)
    {
        switch (type)
        {
            case ThingEnum.A:
                return new A();
                break;
            case ThingEnum.B:
                return new B();
                break;
            case ThingEnum.C:
                return new C();
                break;
            default:
                break;
        }
    }
}

另一个使用抽象和方法重载的方法:

class ThingFactory
{
    public IThing Make(A a)
    {
        return new A();
    }

    public IThing Make(B a)
    {
        return new B();
    }

    public IThing Make(C a)
    {
        return new C();
    }
}

我的问题是:

  1. 哪个实现更快,
  2. 哪个更易读/更容易理解,
  3. 您将使用哪个以及为什么?

【问题讨论】:

  • 您不能根据枚举值进行方法重载。您打算如何区分它们?枚举和返回的类的确切语义是什么?
  • 顺便说一句,IThingFactory 应该被称为 ThingFactory 因为它是一个类而不是一个接口。
  • 尝试编译两者,你会得到答案...
  • @JCode,你的类的名字和 ThingEnum 一样吗?
  • @JCode,因此您需要一个描述关系 enumValue - 类的代码。它们的性能几乎相同,但第一个变体的变体更易于阅读。只需创建单元测试,检查相关类的所有枚举值,如果需要,检查所有 IThing 类的相关枚举值。

标签: c# performance switch-statement overloading


【解决方案1】:

我真的建议您将其作为 IoC 容器。 无论如何,也许你只是有一些你想做的类,以确保在 .ctor 之后的类发生某些事情,这种方法可以工作,你不必使用开关。

class IThingFactory
{
    public IThing MakeThing<T>() where T : IThing, new()
    {
         var thing = new T();
         thing.Init(); // has to be part of the IThing interface.
         return thing;
    }
}

一个更通用的方法是这样

class IThingFactory
{
    private IDictionary<Type, Func<IThing>> factories = new Dictionary<Type, Func<IThing>>();

    public void Register(Type t, Func<IThing> factory);
    {
         if(!typeof(IThing).IsAssignableFrom(t))
             throw new ArgumentException("This is not a thing");
         this.factories.Add(t, factory);
    }

    public void Register<T>() where T : IThing, new()
    {
        this.Register<T>(() => new T());
    }

    public void Register<T>(Func<IThing> factory) where T : IThing
    {
        this.Register(typeof(T), factory);
    }

    public IThing MakeThing(Type type);
    {
        if (!factories.ContainsKey(type))
             throw new ArgumentException("I don't know this thing");
        return factories[type]();
    }
}

public void Main()
{
     var factory = new IThingFactory();
     factory.Register(typeof(A), () => new A());
     factory.Register<B>();
     factory.Register<C>(() => new C("Test"));
     var instance = factory.MakeThing(typeof(A));
}

【讨论】:

    【解决方案2】:

    使用反射更易于维护。

    1. 永远不要使用switch,因为当你添加新类时,你也必须修改这个switch语句。

    2. 第二种方法是不可接受的。 a、b、c 来自哪里?您必须在没有工厂方法的情况下 new 他们。

    同时检查一些IoC containers

    【讨论】:

    • 反射很好,但运行时容易出错,需要正确的约定和一致性,容易失控和陷入麻烦,也不是最快的
    • 我同意。这也是我提到 IoC 容器的原因。感谢您的澄清。
    【解决方案3】:

    你可以这样使用:

    internal static class Factory
    {
        internal static Dictionary<ThingEnum, Func<IThing>> ctors = new Dictionary<ThingEnum, Func<IThing>>
        {
            {ThingEnum.A, () => new A() },
            {ThingEnum.B, () => new B() },
            {ThingEnum.C, () => new C() }
        };
    
        internal static IThing MakeThing(ThingEnum type)
        {
            return ctors[type]();
        }
    }
    

    它比第一个变体更简洁。性能几乎相同。

    【讨论】:

    • 与switch基本相同,只是更复杂,需要与每个下一个子类型一起编辑
    • @mikus,你是对的。对于绑定两种对象的任务,很难得到更简单的答案。第一个变体:手动进行绑定。每个答案只是一个变体。另一种变体:在构建过程中填充反射。获取 IThing 的所有继承者,枚举的值,并通过相同的名称绑定。第三种变体:在运行时使用反射,在工厂创建期间,通过名称绑定继承者和枚举值。最后一个变体有一个问题:名称必须相同。还有更聪明和更困难的变体,但即使是上面的变体也太难了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多