我应该用什么样的创作模式

本文关键字:模式 什么样 我应该 | 更新日期: 2023-09-27 18:06:49

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

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 enum。这会清理很多东西。

我尝试了在dotfactory上提到的创建模式Factory MethodAbstract Factory在这种情况下看起来不合适。

我的代码虽然看起来很丑,但它很容易阅读和理解。我尝试实现上面提到的模式,这增加了代码的复杂性。所以这也是我选择答案的标准。

我不能改变代码中的任何东西,除了Initiator类。所有其他类我都无法编辑。

编辑1:为什么上面的代码在我看来是丑陋的

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

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

首先是T泛型值,其次是enum值。我想避免这个。

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

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

在上面的代码中,枚举值和泛型值是不同的。这是不允许的,而且会造成问题。对于我的代码,我无法避免这种情况。

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

如果我以某种方式去掉enum的必要性,代码将会好很多。如果我能把CreateObject的签名改成如下,那就好多了

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

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

我应该用什么样的创作模式

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

因此,为什么不保持简单,就这样做呢?

public class Initiator
{
    InitVal initVal;
    public A CreateA()
    {
        return new A(initVal);
    }
    public B CreateB()
    {
        return new B(initVal);
    }
}

由于您将该方法指定为泛型方法,我希望您在编译时实际上已经知道您想要获得的类型。所以我要这样写:

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属性指定接口,或者你现在在你的当前构造函数中设置的任何东西。

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

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

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

当然,这只有在你可以在编译时决定类型或者你只是想把这个决定委托给第三方库时才有意义,例如:

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

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

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

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

代码中一个明显的问题是枚举,这是不必要的,因为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();
    }
}

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

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

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

顺便说一句,ObjectInstance对于enum来说是一个非常非常糟糕的名字。

我是这样做的:

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块处理手动错误(如果有的话)。不管怎样,它只适用于我的具体情况;一般不推荐