Cha*_*les 26 polymorphism refactoring conditional
"替换条件与多态"只有在您正在为您选择切换/ if语句的对象类型时才是优雅的.作为一个例子,我有一个Web应用程序,它读取一个名为"action"的查询字符串参数.动作可以具有"视图","编辑","排序"等值.那么如何用多态实现呢?好吧,我可以创建一个名为BaseAction的抽象类,并从中派生ViewAction,EditAction和SortAction.但是,我不需要条件来决定实例化哪种类型的BaseAction?我不知道如何用多态完全替换条件.如果有的话,条件只会被推到链的顶端.
编辑:
public abstract class BaseAction
{
public abstract void doSomething();
}
public class ViewAction : BaseAction
{
public override void doSomething() { // perform a view action here... }
}
public class EditAction : BaseAction
{
public override void doSomething() { // perform an edit action here... }
}
public class SortAction : BaseAction
{
public override void doSomething() { // perform a sort action here... }
}
string action = "view"; // suppose user can pass either "view", "edit", or "sort" strings to you.
BaseAction theAction = null;
switch (action)
{
case "view":
theAction = new ViewAction();
break;
case "edit":
theAction = new EditAction();
break;
case "sort":
theAction = new SortAction();
break;
}
theAction.doSomething(); // So I don't need conditionals here, but I still need it to decide which BaseAction type to instantiate first. There's no way to completely get rid of the conditionals.
Run Code Online (Sandbox Code Playgroud)
Car*_*ter 28
你是对的 - "条件被推到了链条的顶端" - 但是没有"只是"它.它非常强大.正如@thkala所说,你只做出一次选择; 从那以后,对象知道如何开展业务.您描述的方法--BaseAction,ViewAction和其他方法 - 是一种很好的方法.试一试,看看您的代码变得多清晰.
如果你有一个工厂方法,它接受一个像"查看"这样的字符串并返回一个Action,你调用它,你已经隔离了你的条件.那很棒.并且你不能正确地欣赏它的力量,直到你尝试过 - 所以试一试!
尽管最后一个答案是一年前,但我想对这个主题做一些评论/评论.
答案评论
我同意@CarlManaster关于编写switch语句一次以避免处理重复代码的所有众所周知的问题,在这种情况下涉及条件(其中一些是@thkala提到的).
我不相信@KonradSzałwiński或@AlexanderKogtenkov提出的方法适合这种情况有两个原因:
首先,从您描述的问题中,您不需要动态更改操作名称与处理操作的操作实例之间的映射.
请注意,这些解决方案允许这样做(通过简单地将操作名称分配给新的操作实例),而基于静态交换机的解决方案则不允许(映射是硬编码的).此外,您仍然需要条件来检查映射表中是否定义了给定键,如果不是应该采取操作(defaultswitch语句的一部分).
其次,在这个特定的例子中,dictionaries真的是switch语句的隐藏实现.更重要的是,使用default子句读取/理解switch语句可能比从心理上执行从映射表返回处理对象的代码更容易,包括处理未定义的键.
有一种方法可以摆脱所有条件,包括switch语句:
删除switch语句(根本不使用条件)
如何从动作名称创建正确的动作对象?
我将与语言无关,所以这个答案不会那么久,但诀窍是实现类也是对象.
如果你已经定义了一个polimorphic层次结构,那么引用一个具体的子类是没有意义的BaseAction:为什么不让它返回正确的实例来处理它的名字呢?
这通常是通过您编写的相同switch语句(例如,工厂方法)实现的......但是这样做:
public class BaseAction {
//I'm using this notation to write a class method
public static handlingByName(anActionName) {
subclasses = this.concreteSubclasses()
handlingClass = subclasses.detect(x => x.handlesByName(anActionName));
return new handlingClass();
}
}
Run Code Online (Sandbox Code Playgroud)
那么,那个方法在做什么呢?
首先,检索此的所有具体子类(指向BaseAction).在您的例子中,你还是会回到一个集合ViewAction,EditAction和SortAction.
请注意,我说的是具体的子类,而不是所有的子类.如果层次结构更深,具体的子类将始终是层次结构底部的那些(叶子).那是因为他们是唯一不应该是抽象的并且提供真正实现的人.
其次,获取第一个子类,它回答它是否可以通过其名称处理一个动作(我使用的是lambda/closure风格的表示法).handlesByName类方法的示例实现ViewAction看起来像:
public static class ViewAction {
public static bool handlesByName(anActionName) {
return anActionName == 'view'
}
}
Run Code Online (Sandbox Code Playgroud)
第三,我们将消息new发送给处理动作的类,有效地创建它的实例.
当然,当子类没有按名称处理动作时,你必须处理这种情况.许多编程语言(包括Smalltalk和Ruby)允许将detect方法传递给第二个lambda /闭包,只有在没有子类符合条件时才会对其进行求值.此外,您将不得不处理多个子类通过其名称处理操作的情况(可能,其中一个方法以错误的方式编码).
结论
这种方法的一个优点是可以通过编写(而不是修改)现有代码来支持新操作:只需创建一个新的子类BaseAction并handlesByName正确实现类方法.它有效地支持通过添加新概念添加新功能,而无需修改现有的实现.很明显,如果新功能需要将新的polimorphic方法添加到层次结构中,则需要进行更改.
此外,您可以使用系统反馈向开发人员提供:"提供的操作不由BaseAction的任何子类处理,请创建新的子类并实现抽象方法".对我来说,模型本身告诉你什么是错的(而不是试图在心理上执行查找表)这一事实增加了价值并明确了必须完成的工作.
是的,这可能听起来过于设计.请保持开放的心态,并意识到解决方案是否过度设计必须与您正在使用的特定编程语言的开发文化一起进行.例如,.NET人员可能不会使用它,因为.NET不允许您将类视为真实对象,而另一方面,该解决方案用于Smalltalk/Ruby文化.
最后,使用常识和品味来预先确定特定技术在使用之前是否真正解决了您的问题.这是诱人的,但所有的权衡(文化,开发人员的资历,对变革的抵制,开放的思想等)都应该进行评估.
需要考虑的一些事项:
你只实例化对象的每一次.一旦这样做,就其类型不再需要条件.
即使在一次性实例中,如果你使用子类,你会摆脱多少个条件?使用像这样的条件的代码很容易一次又一次地充满完全相同的条件......
foo将来需要Action值时会发生什么?你需要修改多少个地方?
如果你需要一个bar只是略有不同的foo怎么办?带班,你只是继承BarAction从FooAction,覆盖了一件事,你需要改变.
从长远来看,面向对象的代码通常比程序代码更易于维护-大师们不会有任何问题,但对于我们其余的人有是有差别.