我应该使用什么创作模式?
What creational pattern I should use?
我的程序有两个class;两者都来自相同的基数 class.
class A : MyBase
{
internal A(InitVal initVal)
}
class B : MyBase
{
internal B(InitVal initVal)
}
InitVal
是另一个通过构造函数注入的 class 。 class 供内部使用。由于内部构造函数,用户无法直接创建 class A
和 B
的实例。相反,我创建了创建这些对象的方法。
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
枚举。它会清理很多。
我尝试了 Creational Patterns dotfactory 中提到的。
Factory Method
和 Abstract Factory
在这种情况下看起来不合适。
我的代码虽然看起来很丑,但阅读和理解起来非常简单。我尝试实现上面提到的模式,这增加了代码的复杂性。所以这也是我选择答案的标准。
除了 Initiator
class,我无法更改代码中的任何内容。我无法访问所有其他 classes 进行编辑。
编辑 1:为什么上面的代码在我看来很丑
1) 在调用CreateObject
方法时,用户必须指定对象的类型两次。
A a = initiator.CreateObject<A>(ObjectInstance.A);
首先是 T
通用值,其次是枚举值。
我想避免这种情况。
2) 由于用户必须指定对象的类型两次,因此有可能出错。
A a = initiator.CreateObject<A>(ObjectInstance.B);
在上面的代码中,枚举值和通用值是不同的。
这是不允许的,而且会成为一个问题。
使用我的代码,我无法避免这一点。
这就是原因;我正在寻找适合我的情况而又不增加复杂性的模式。
如果我以某种方式消除枚举的必要性,代码会好很多。
如果能把CreateObject
的签名改成following就更好了
public T CreateObject<T>() where T : MyBase
但是,我不确定我将如何实施此方法来创建适当的实例。
当您将方法指定为通用方法时,我希望您实际上可能在编译期间就知道您想要获得的类型。所以我会选择这样的方法:
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
要使其正常工作,您需要在 类 中指定一个内部 无参数构造函数 ,并为值指定 接口 属性 或您现在在当前构造函数中设置的任何内容。
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 模式,它可以完成工作。只是其中的泛型参数……似乎没用了。我会删除它并将 return 类型更改为 MyBase,因为用户无论如何都无法指定 T。
最后一个选择是简单地为每种类型创建一个单独的方法,这是干净、灵活的,提供了很多自定义选项,但如果需要重复就很糟糕很多共享逻辑,您需要为每种下一种类型添加一个新逻辑。只需:
A CreateObjectA(InitVal initValue){
return new A(initValue);
}
B CreateObjectB(InitVal initValue){ ...
在我看来,您尝试将其通用化并没有获得任何好处。需要在调用处知道返回值的具体类型
因此,为什么不让事情简单化,就这样做呢?
public class Initiator
{
InitVal initVal;
public A CreateA()
{
return new A(initVal);
}
public B CreateB()
{
return new B(initVal);
}
}
您的代码的一个明显问题是枚举,这是不必要的,因为 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();
}
}
或者,如果您不知道类型,可以传递枚举,但是 return 类型应该是 interface/base class(最好是接口):
// 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();
}
现在你有一个简单的 "poor man's" DI class 叫做 Initiator
,所以我想知道你的 DI 框架(注入 InitVal
的框架)是否也可以注入A
和 B
个实例。这可能是正确的,因为 DI 纯粹主义者会告诉您工厂和代码中没有 new
关键字的位置。
顺便说一句,ObjectInstance
是一个非常非常糟糕的枚举名称。
我是按以下方式做的:
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
块处理手动错误(如果有)。无论如何,它只适用于我的具体情况; 一般不推荐。
我的程序有两个class;两者都来自相同的基数 class.
class A : MyBase
{
internal A(InitVal initVal)
}
class B : MyBase
{
internal B(InitVal initVal)
}
InitVal
是另一个通过构造函数注入的 class 。 class 供内部使用。由于内部构造函数,用户无法直接创建 class A
和 B
的实例。相反,我创建了创建这些对象的方法。
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
枚举。它会清理很多。
我尝试了 Creational Patterns dotfactory 中提到的。
Factory Method
和 Abstract Factory
在这种情况下看起来不合适。
我的代码虽然看起来很丑,但阅读和理解起来非常简单。我尝试实现上面提到的模式,这增加了代码的复杂性。所以这也是我选择答案的标准。
除了 Initiator
class,我无法更改代码中的任何内容。我无法访问所有其他 classes 进行编辑。
编辑 1:为什么上面的代码在我看来很丑
1) 在调用CreateObject
方法时,用户必须指定对象的类型两次。
A a = initiator.CreateObject<A>(ObjectInstance.A);
首先是 T
通用值,其次是枚举值。
我想避免这种情况。
2) 由于用户必须指定对象的类型两次,因此有可能出错。
A a = initiator.CreateObject<A>(ObjectInstance.B);
在上面的代码中,枚举值和通用值是不同的。 这是不允许的,而且会成为一个问题。 使用我的代码,我无法避免这一点。
这就是原因;我正在寻找适合我的情况而又不增加复杂性的模式。
如果我以某种方式消除枚举的必要性,代码会好很多。
如果能把CreateObject
的签名改成following就更好了
public T CreateObject<T>() where T : MyBase
但是,我不确定我将如何实施此方法来创建适当的实例。
当您将方法指定为通用方法时,我希望您实际上可能在编译期间就知道您想要获得的类型。所以我会选择这样的方法:
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
要使其正常工作,您需要在 类 中指定一个内部 无参数构造函数 ,并为值指定 接口 属性 或您现在在当前构造函数中设置的任何内容。
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 模式,它可以完成工作。只是其中的泛型参数……似乎没用了。我会删除它并将 return 类型更改为 MyBase,因为用户无论如何都无法指定 T。
最后一个选择是简单地为每种类型创建一个单独的方法,这是干净、灵活的,提供了很多自定义选项,但如果需要重复就很糟糕很多共享逻辑,您需要为每种下一种类型添加一个新逻辑。只需:
A CreateObjectA(InitVal initValue){
return new A(initValue);
}
B CreateObjectB(InitVal initValue){ ...
在我看来,您尝试将其通用化并没有获得任何好处。需要在调用处知道返回值的具体类型
因此,为什么不让事情简单化,就这样做呢?
public class Initiator
{
InitVal initVal;
public A CreateA()
{
return new A(initVal);
}
public B CreateB()
{
return new B(initVal);
}
}
您的代码的一个明显问题是枚举,这是不必要的,因为 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();
}
}
或者,如果您不知道类型,可以传递枚举,但是 return 类型应该是 interface/base class(最好是接口):
// 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();
}
现在你有一个简单的 "poor man's" DI class 叫做 Initiator
,所以我想知道你的 DI 框架(注入 InitVal
的框架)是否也可以注入A
和 B
个实例。这可能是正确的,因为 DI 纯粹主义者会告诉您工厂和代码中没有 new
关键字的位置。
顺便说一句,ObjectInstance
是一个非常非常糟糕的枚举名称。
我是按以下方式做的:
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
块处理手动错误(如果有)。无论如何,它只适用于我的具体情况; 一般不推荐。