工厂方法是在创建某些对象族时隐藏复杂性的好方法。精细。但是当工厂方法本身开始变得复杂时会发生什么?
例如,我需要基于两个或多个标志/属性/值创建一个对象,如下所示:
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
}
}
}
}
这很快变得丑陋。好的,所以你隐藏了客户的复杂性,但你的工厂代码正在成为维护上的头痛。
此问题有哪些其他解决方案?是否存在某种多值查找模式可能使其更容易阅读和维护?
答案 0 :(得分:4)
把这些碎片放在一起。 :)
将参数映射到匿名创建者。
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()
}
答案 1 :(得分:2)
根据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是否具有相似的功能(虽然我确定它有)
答案 2 :(得分:2)
如果您的规则变得比简单的属性比较更复杂,您可能希望留下一个选项来做更多的事情,例如:
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
解决方案相比的速度差异(但这两种方法实际上并不能实现因为hashset只能进行相等比较)。
除此之外,其用法类似于Andrew's solution:
IMyClass instance = MyClassFactory.Create(propSet);
答案 3 :(得分:1)
您可以将其转换为从属性元组(封装在适当的类中)到类名的映射,然后可以使用反射对其进行实例化。或者,地图的层次结构。在Java中,我将使用Spring在XML配置文件中定义这样的映射;可能在C#中也有类似的方法来实现这一点。
答案 4 :(得分:0)
有很多选择,但这取决于它如何扩展。条件有哪些其他值/逻辑?
一种选择是使用组合swich
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 ...;
}
答案 5 :(得分:0)
如果这是无益的,我道歉,但我无法抗拒地指出这种东西在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#中不存在。