过于复杂的工厂方法-任何解决方案
本文关键字:方法 工厂 任何 解决方案 于复杂 复杂 | 更新日期: 2023-09-27 17:53:23
工厂方法是在创建某些对象族时隐藏复杂性的好方法。很好。但是当工厂方法本身开始变得复杂时会发生什么呢?
例如,我需要基于两个或多个标志/属性/值创建一个对象,如下所示:public class MyClassFactory
{
public static IMyClass GetClass(int prop1, int prop2, int prop3)
{
switch(prop1)
{
case (1):
switch(prop2)
{
case(1):
if(prop3 == x)
return new ImplMyClassA();
else
return new ImplMyClassB();
...
//etc ad infinitum
}
}
}
}
这很快就会变得很难看。好的,所以你对客户隐藏了复杂性,但是你的工厂代码正在成为维护的头痛问题。
对于这个问题还有其他的解决方案吗?是否存在某种多值查找模式,使其更易于阅读和维护?
把这些片段放在一起。:)
参数映射到匿名创建者。
Class propertySet{
int prop1
int prop2
int prop3
public equals(..){... }
}
interface creator {
IMyClass create()
}
Map<propertySet, classCreator> creatorsMap = new HashMap();
static {
creatorsMap.add(new propertySet(1,2,3), new creator() { ... create() {return ImplMyClassA(); } });
......
creatorsMap.add(new propertySet(7,8,9), new creator() { ... create() {return ImplMyClassB();} });
}
public IMyClass create(x,y,z) {
return creatorsMap.get(new propertySet(x,y,z)).create()
}
如果你的规则变得比简单的属性比较更复杂,你可能想要留下一个选项来做更多的事情,例如:
interface IFactoryRule
{
bool CanInstantiate(PropertySet propSet);
}
简单的实现是这样的:
// compares property set to given parameters
public SimpleRule : IFactoryRule
{
private readonly int a,b,c;
public SimpleRule(a,b,c) { ... }
public bool CanInstantiate(PropertySet propSet)
{
return
propSet.a == a &&
propSet.b == b &&
propSet.c == c;
}
}
但是您也可以创建任何类型的复杂自定义规则:
// compares property set using a delegate
public ComplexRule : IFactoryRule
{
private readonly Func<PropertySet, bool> _func;
public ComplexRule(func) { ... }
public bool CanInstantiate(PropertySet propSet)
{
return _func(propSet);
}
}
添加所有类型的决策到你的工厂:
public class MyClassFactory
{
private static List<Tuple<IFactoryRule, Func<IMyClass>>> _rules = new List();
static MyClassFactory()
{
// rules are evaluated in this same order
_rules.Add(new SimpleRule(1,2,3), () => new Simple());
_rules.Add(new ComplexRule(p => p.a + p.b == p.c), () => new Complex());
}
public static IMyClass Create(PropertySet pset)
{
if (pset == null)
throw new ArgumentNullException("pset");
// try to find a match
Tuple<IFactoryRule, Func<IMyClass>> rule =
_rules.FirstOrDefault(r => r.First.CanInstantiate(pset));
if (rule == null)
throw new ArgumentException(
"Unsupported property set: " + pset.ToString());
// invoke constructor delegate
return rule.Second();
}
}
(编辑:添加了MyClassFactory.Create
方法)
正如你所看到的,在这个解决方案中没有规则的哈希映射,所以在Create
方法中规则列表逐个求值(FirstOrDefault
将迭代列表直到找到第一个匹配)。
如果你有很多规则(比如超过20条),并且你正在实例化一百万个对象,你会注意到与HashSet
解决方案相比的速度差异(但是这两种方法实际上不能比较,因为哈希集只能进行相等比较)。
除此之外,用法类似于Andrew的解决方案:
IMyClass instance = MyClassFactory.Create(propSet);
根据这3个属性的性质,您可以创建定义每个类的自定义属性。所以你会有这样的类:
[IMyClass(Prop1 = 1, Prop2 = 3, Prop3 = 4)]
public class Blah : IMyClass
[IMyClass(Prop1 = 4, Prop2 = 5, Prop3 = 6)]
public class Blah2 : IMyClass
然后在你的工厂方法中,你可以使用反射来循环IMyClass
的所有实现,检索它们的IMyClassAttribute
实例,并检查属性是否匹配。这使您不必在两个地方保持映射,并将类的映射包含在类定义本身中。
编辑供参考,这是基于c#的,我不知道Java是否有类似的功能(尽管我确信它有)
您可以将其转换为从属性元组(封装在类中)到类名的映射,然后可以使用反射对其进行实例化。或者,映射的层次结构。在Java中,我会使用Spring在XML配置文件中定义这样的映射;在c#中可能也有类似的方法来实现这一点。
有很多选择,但这取决于如何缩放。条件还有什么其他值/逻辑?
一个选项是使用组合开关
public static IMyClass getClass(int prop1, int prop2, int prop3) {
switch(prop1*1000000+prop2*1000+prop3) {
case 1001001:
}
}
一个选项是使用反射。
public static IMyClass getClass(int prop1, int prop2, int prop3) {
Method m = MyClassFactory.class.getMethod("create"+prop1+"_"+prop2+"_"+prop3);
return (IMyClass) m.invoke(this);
}
public static IMyClass create1_1_1() {
return new ...;
}
如果这没有帮助,我很抱歉,但我忍不住要指出,这类事情在f#中很容易编写,并且可以很容易地从c#中调用。事实上,这个f#代码的签名与问题中的示例方法的签名相同——它看起来与调用它的c#代码相同。
module MyClassFactory =
let GetClass = function
| 1, 1, 1 -> ImplMyClassA() :> IMyClass
| 1, 1, 2 -> ImplMyClassB() :> IMyClass
| 1, 1, _ -> ImplMyClassC() :> IMyClass
| 2, 1, 1 -> ImplMyClassD() :> IMyClass
| 2, 1, _ -> ImplMyClassE() :> IMyClass
| 2, 2, _ -> ImplMyClassF() :> IMyClass
| _ -> ImplMyClassDefault() :> IMyClass
如果你不能使用MEF我猜你也不能使用f#。然而,实际上存在"某种多值查找模式,可能会使它更容易阅读和维护"-它只是不存在于c#中。