【问题标题】:What creational pattern I should use?我应该使用什么创建模式?
【发布时间】:2016-09-13 07:20:51
【问题描述】:

我的程序有两个类;两者都派生自同一个基类。

class A : MyBase
{
    internal A(InitVal initVal)
}

class B : MyBase
{
    internal B(InitVal initVal)
}

InitVal 是另一个通过构造函数注入的类。此类供内部使用。由于内部构造函数,用户无法直接创建类AB 的实例。相反,我创建了创建这些对象的方法。

class Initiator
{
    InitVal initVal;

    public T CreateObject<T>(ObjectInstance objectInstance) where T : MyBase
    {
        MyBase myBase = null;
        switch(objectInstance)
        {
            case ObjectInstance.A:
                myBase = new A(initVal);
                break;
            case ObjectInstance.B:
                myBase = new B(initVal);
                break;
        }
        return (T)myBase;
    }
    ...
}

ObjectInstance 是上面代码中的枚举。

这没有问题,但我相信你以前从未见过如此丑陋的代码。

请建议我应该使用的创作模式。我想删除 ObjectInstance 枚举而不更改功能。它会清理很多。

我尝试了dotfactory 中提到的Creational PatternsFactory MethodAbstract Factory 在这种情况下看起来不合适。

我的代码虽然看起来很丑,但读起来很简单。我尝试实现上述增加代码复杂性的模式。所以这也是我选择答案的标准。

除了Initiator 类之外,我无法更改代码中的任何内容。我无法访问所有其他类进行编辑。

编辑 1:为什么上面的代码在我看来很难看

1) 在调用CreateObject 方法时,用户必须两次指定对象的类型。

A a = initiator.CreateObject<A>(ObjectInstance.A);

首先是T 通用值,其次是枚举值。 我想避免这种情况。

2) 由于用户必须两次指定对象类型,因此有可能出错。

A a = initiator.CreateObject<A>(ObjectInstance.B);

在上面的代码中,枚举值和通用值是不同的。 这是不允许的,这将是一个问题。 使用我的代码,我无法避免这种情况。

这就是为什么;我正在寻找适合我的情况而又不增加复杂性的模式。

如果我以某种方式消除枚举的必要性,代码会好得多。 如果我能把CreateObject的签名改成关注就更好了。

public T CreateObject<T>() where T : MyBase

但是,我不确定如何实现这个方法来创建合适的实例。

【问题讨论】:

  • 如果您无法更改AB,那么人们仍然可以创建A 和/或B 的实例。那为什么还要打扰整个Initiator 类呢?
  • 如果您不希望 API 的使用者实例化类,我会将构造函数设为内部
  • 您的代码并没有那么难看,这正是工厂方法所做的:它根据某些不同的值创建类型的实例,在您的情况下是 enum。如果添加了新类,您也可以通过配置文件执行此操作,而不必更改源代码。只有真正丑的是你的缩进。
  • 当前Initiator 类的一个问题是用户现在必须构造Initiator 的实例,并且必须以某种方式设置initVal。字段是如何设置的?
  • 另一种方式,更简洁的方法是简单地使用 2 种方法来创建 A 或 B,第 3 方方法将选择其中一种。或者将其作为一种方法实现,并通过指定泛型类型来决定类型。否则它只是一个工厂方法,它看起来很好。 PS。将启动器设为静态,并将 initVal 设为最有可能的参数:P

标签: c# design-patterns creation-pattern


【解决方案1】:

在我看来,你并没有从试图使这个通用化中获得任何好处。您需要知道调用点返回值的具体类型。

因此,为什么不把事情简单化,就这样做呢?

public class Initiator
{
    InitVal initVal;

    public A CreateA()
    {
        return new A(initVal);
    }

    public B CreateB()
    {
        return new B(initVal);
    }
}

【讨论】:

  • @MatthewWatson,它不是真正的工厂方法,因为工厂方法根据定义决定类型,那些不决定任何东西,只是构造始终相同的类型。但是好吧,谁在乎命名是否有效
【解决方案2】:

当您将该方法指定为通用方法时,我希望您实际上可能知道在编译期间您想要获得的类型。所以我会选择这样的方法:

class Initiator
{ 
    public T CreateObject<T>(ObjectInstance objectInstance) where T : MyBase, new()
    {
        T newInstance = new T();
        newInstance.Value = initVal;

        return newInstance;
    }
...
}

现在你可以这样称呼它:

A myAInstance = initiator.CreateObject<A>();
MyBase myAInstance = initiator.CreateObject<A>();   //this also works

要使其工作,您需要在类中指定一个内部无参数构造函数,并为 Value 属性指定 interface 或您现在在当前构造函数中设置的任何内容。

class MyBase{
    InitVal Value { get; set;}       //this allows construction of the object with parameterless constructor
    ...
}

这不仅更简洁,而且更不容易出错,因为您不需要在每次添加新类型时同时编辑枚举和方法体。但是,它为子类型特定逻辑提供了较少的灵活性。

注意:如果您真的想要像现在一样使用带参数的构造函数,您仍然可以采用这种方法,但您需要使用 reflection(检查 Activator)或 lambdas。

当然,这只有在您可以在编译期间决定类型或者您只想将此决定委托给第 3 方库时才有意义,例如:

switch(chosenType){
case ObjectInstance.A:
    instance = initiator.CreateObject<A>();
    ...

否则,只需保持原样,它或多或少是一个 FactoryMethod 模式,它可以完成工作。只是它里面的泛型参数......那时似乎没什么用。我将删除它并将返回类型更改为 MyBase,因为用户无论如何都无法指定 T。

最后一个选择是简单地为每种类型创建一个单独的方法,这很干净,灵活,提供了很多自定义选项,但是如果您需要重复很多共享逻辑,那就太糟糕了并且您需要为每种下一种类型添加一个新的。简单地说:

A CreateObjectA(InitVal initValue){
     return new A(initValue);
}
B CreateObjectB(InitVal initValue){ ...

【讨论】:

    【解决方案3】:

    您的代码的一个明显问题是枚举,这是不必要的,因为typeof(T) 已经为您提供了适当的类型:

    class Initiator
    {
        readonly Dictionary<Type, Func<MyBase>> _dict = new Dictionary<Type, Func<MyBase>>();
    
        internal Initiator(InitVal initVal)
        {
            // initialize your "service locator".
            // it's cool that different types can have different constructors,
            // and people who call CreateObject don't need to know this.
            _dict[typeof(A)] = (Func<MyBase>)(() => new A(initVal));
            _dict[typeof(B)] = (Func<MyBase>)(() => new B(initVal, someOtherStuff));
        }
    
        public T CreateObject<T>() where T : MyBase
        {
            var ctor = _dict[typeof(T)];
            return (T)ctor();
        }
    }
    

    或者,如果你不知道类型,你可以传递枚举,但是返回类型应该是接口/基类(最好是接口):

    // this is more likely, you probably don't need a generic method
    public IMyBase CreateObject(ObjectInstance objectInstance)
    {
        // the dictionary would map enum value to Func<IMyBase>, of course
        var ctor = _dict[objectInstance];
        return ctor();
    }
    

    现在你有一个名为Initiator 的简单“穷人”DI 类,所以我想知道你的DI 框架(注入InitVal 的那个)是否也可以注入AB 实例。这可能是真的,因为 DI 纯粹主义者会告诉您,在您的代码中没有工厂和 new 关键字的位置。

    顺便说一句,ObjectInstance 是一个非常非常糟糕的枚举名称。

    【讨论】:

      【解决方案4】:

      我是通过以下方式做到的:

      class A : IMyType
      {
          internal A(InitVal initVal)
      }
      
      class B : IMyType
      {
          internal B(InitVal initVal)
      }
      
      class Initiator
      {
          InitVal initVal = .....;
      
          public T CreateObject<T>() where T : IMyType
          {
              IMyType myType = null;
              if(typeof(T) == typeof(A))
                  myType = new A(initVal);
              else if(typeof(T) == typeof(B))
                  myType = new B(initVal);
              else
                  throw new MyException("Type is not configured.");
              return (T)myType;
          }
          ...
      }
      

      这解决了我在问题中提到的问题。但是,它会产生新的问题。 这违反了 SOLID 的开闭原则。最后一个else 块处理手动错误(如果有)。无论如何,它只适用于我的具体情况; 不推荐一般。

      【讨论】:

        猜你喜欢
        • 2017-12-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-11-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多