我一直在阅读有关设计模式的一些内容,并想要一些观点。请考虑以下事项:
Dim objGruntWorker as IGruntWorker
if SomeCriteria then
objGruntWorker = new GoFor()
else if SomeOtherCriteria then
objGruntWorker = new Newb()
else if SomeCriteriaAndTheKitchenSink then
objGruntWorker = new CubeRat()
end if
objGruntWorker.GetBreakfast()
system.threading.thread.sleep(GetMilliSecondsFromHours(4))
objGruntWorker.GetLunch()
每次出现新标准时,上述代码都会增长。我已经看到了这样的代码,并且无知中自己写了一些代码。该如何解决?这种反模式是否具有更“正式”的名称?谢谢你的帮助!
编辑:另一个考虑因素是我想避免重新编译IGruntWorker
的现有实现,只是为了添加新的实现。
答案 0 :(得分:7)
这种逻辑通常使用Factory method pattern进行封装。 (请参阅Encapsulation下的ImageReaderFactory示例。)
答案 1 :(得分:5)
适合上述解决方案的模式类型为Factory Pattern。在某种情况下,您不需要知道所需的具体对象类型,只需实现IGruntWorker
即可。因此,您创建一个接受标准的工厂,并根据该条件返回特定的IGruntWorker
对象。将标准映射到某个标识符(即枚举或常数以便于阅读)通常是个好主意,例如
public enum WorkerType
{
Newbie,
Average,
Expert
}
public class WorkerFactory
{
public static IGruntWorker GetWorker(WorkerType type)
{
switch (type)
{
case WorkerType.Newbie:
return new NewbieWorker();
case WorkerType.Average:
return new AverageWorker();
case WorkerType.Expert:
return new ExpertWorker();
}
}
}
因此,在您的情况下,您可以使用一个小帮助方法,根据条件确定正确的工作类型。这甚至可以包含在您只是传递到工厂的只读属性中。
答案 2 :(得分:5)
您可以为每个对象类型创建工厂,并且这些工厂可以具有将标准作为参数的函数,并且如果满足参数则返回IGruntWorker(否则返回null)。
然后你可以创建一个这些工厂的列表并循环遍历它们(对不起,我是一个c#人):
Dim o as IGruntWorker;
foreach (IGruntWorkerFactory f in factories)
{
o = f.Create(criterias);
if (o != null)
break;
}
当需要新标准时,您只需将其添加到工厂列表中,无需修改循环。
可能有一些更美丽的方式
我的2美分
答案 3 :(得分:2)
如果您使用的是.NET,则可以使用反射来构建它。例如,如果您正在创建一个插件系统,那么您将有一个文件夹来删除插件DLL。然后你的工厂会查看可用的DLL,检查每个DLL是否有适当的反射属性,然后将这些属性与传入的任何字符串相匹配,以决定选择和调用哪个对象。
这使您无需重新编译主应用程序,但是您必须在其他DLL中构建您的工作人员,然后有办法告诉您的工厂使用哪个。
这里有一些非常快速和脏的伪代码来解决这个问题:
假设您有一个名为Workers.DLL的DLL程序集
使用名为Name的字符串属性设置名为WorkerTypeAttribute的属性,以及能够设置Name属性的构造函数。
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class WorkerTypeAttribute : Attribute
{
string _name;
public string Name { get { return _name; } }
public WorkerTypeAttribute(string Name)
{
_name = Name;
}
}
然后,您可以将此属性应用于您定义的任何工作类:
[WorkerType("CogWorker")]
public class CogWorker : WorkerBase {}
然后在您应用的工作工厂中,您将编写如下代码:
public void WorkerFactory(string WorkerType)
{
Assembly workers = Assembly.LoadFile("Workers.dll");
foreach (Type wt in workers.GetTypes())
{
WorkerTypeAttribute[] was = (WorkerTypeAttribute[])wt.GetCustomAttributes(typeof(WorkerTypeAttribute), true);
if (was.Count() == 1)
{
if (was[0].Name == WorkerType)
{
// Invoke the worker and do whatever to it here.
}
}
}
}
我确信还有其他一些如何做到这一点的例子,但是如果你需要更多指针,请告诉我。关键是你的所有工人都需要有一个共同的父或接口,以便你可以用同样的方式调用它们。 (即所有工作人员都需要一个通用的“执行”方法或可以从工厂调用的东西,或者在任何使用该对象的地方。
答案 4 :(得分:1)
如果你可以使用checkCriteria
方法定义一个对象,那么你可以使这个代码由表驱动。我不知道C#,所以请耐心等待我:
public class WorkerFactory {
IGruntWorker makeWorkerIfCriteria(criteria_parameters parms);
}
extern WorkerFactory worker_factories[]; /* table with factories in order */
IGruntWorker makeJustTheRightWorker(criteria_parameters actual_critera) {
for (i = 0; i < worker_factories.length(); i++) {
IGruntWorwer w = worker_factories[i].makeWorker(actual_criteria);
if (!null(w)) return w;
}
--- grim error --- /* table not initiailized correctly */
}
然后表中的一些对象看起来像这样
public class MakeGoFor(critera_parameters cp) {
if SomeCriteria then
return new GoFor();
else
return NULL;
}
您可以在单独的模块中重新编译表,而无需重新编译选择代码。事实上,如果你有野心,你甚至可以在运行时根据命令行参数或文件内容构建表...
答案 5 :(得分:1)
您可以使用访客模式的变体吗?称之为工厂访客(也许)
原谅伪代码,但我的VB生锈了
Dim objGruntWorker as IGruntWorker
objGruntWorker = null
// all your objects implement IFactoryVisitor
Dim factory as IFactoryVisitor
while objGruntWorker == null
factory = factoryCollection.GetNext
objGruntWorker = factory.TryBuild(...)
end
objGruntWorker.GetBreakfast()
system.threading.thread.sleep(GetMilliSecondsFromHours(4))
objGruntWorker.GetLunch()
答案 6 :(得分:0)
我认为只要您最有可能的标准是先订购,以允许运行时跳过剩下的情况,这很好。
如果你的顾虑只是可读性,你可以使用三元运算符,或者如果标准评估只是==,你可以使用switch语句。
答案 7 :(得分:0)
我认为这种模式很好,只要你的标准和操作是单行/方法调用。这很容易阅读并准确反映您的逻辑:
if (ConditionOne())
{
BuildTheWidget();
}
else if (ConditionTwo())
{
RaiseTheAlarm();
}
else if (ConditionThree())
{
EverybodyGetsARaise();
}
即使有20种不同的条件,它也可能准确反映了您应用程序的一些复杂业务逻辑。
另一方面,这是一个可读性灾难
if ( ((A && B) || C &&
(D == F) || (F == A)))
{
AA;
BB;
//200 lines of code
}
else if ( (A || D) && B)
{
// 200 more lines
}
答案 8 :(得分:0)
我认为很多都取决于你的'条件'是多么可预测。你的'成长IF'本质上是一个工厂,也许重构它自己的方法或类会有所帮助,但它可能仍然是一个成长的IF。如果你的条件是你无法预测的事情,比如“if joe.is.on.fire”或“if x == 2”或“if!shuttle.is.launched”那么你就会被IF所困扰。
这些uber-IF的一个坏处是它们可能对您的应用程序产生的范围。也就是说,你需要打电话/触摸/检查以确定哪个'如果'应该是真的?你最终可能会有大量的全球性或大量参数传递到你的“工厂”。我之前做过的一件事就是实现一个包含boolean-delegates(Func)和类型数组的各种工厂。我会在初始化时注册布尔委托和类型,并在工厂中通过调用每个委托的列表进行迭代,直到我得到'true'然后实例化该类型。这对我来说效果很好,因为我能够在不编辑工厂的情况下“注册”新条件。
只是一个想法
答案 9 :(得分:0)
我知道你的.NET,但这就是我在Java web应用程序中做类似的事情,我的'if-thens'正在增长....仍需要重新编译但很容易添加其他操作或在你的情况下grunt工人。
private HashMap actionMap = new HashMap();
actionMap.put("cubeRat", new CubeRatAction());
actionMap.put("newb", new NewbAction());
actionMap.put("goFor", new goForAction());
actionMap.put("other", new otherAction());
String op = request.getParameter("criteria"); // not sure how your criteria is passed in but this is through a parameter in my URL.
ControllerAction action = (ControllerAction) actionMap.get(op);
if (action != null) {
action.GetBreakfast();
action.Sleep();
action.GetLunch();
} else {
String url = "views/errorMessage_v.jsp";
String errMessage = "Operation '" + op + "' not valid for in '" + request.getServletPath() + "' !!";
request.setAttribute("message", errMessage);
request.getRequestDispatcher(url).forward(request, response);
}
答案 10 :(得分:0)
您可以使用反射来查找给定类型的构造函数,并通过构造函数创建实例。原因,施工人员必须遵循某种模式。在上面的示例中,所有都是默认构造函数。