2011-02-01 31 views
19

只有当您正在做的对象类型为switch/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. 
+0

请提供一些更多的代码,如果你正在寻找一个多态性教程,你可能会发现它在网络上的任何地方。请举一些例子。 – amirouche 2011-02-01 19:18:50

+0

无法在此处添加所有代码。我在下面提供了一个示例(查找Charles) – Charles 2011-02-01 20:09:11

+0

您可以使用反射api替换剩余的条件。根据字符串在运行时确定要实例化的基本动作的类型。有一个倒下来,你的字符串应该与你的基本动作的确切名称相匹配。您还可以使用哈希映射将基本操作映射到字符串,并在运行时进行选择。 – 2016-03-21 05:14:15

回答

23

你是对的 - “条件越来越被推到链条的顶端” - 但是没有“正义”。它非常强大。正如@thkala所说,你只需做出一次;从那里出来,对象知道如何去做它的业务。你描述的方法--BaseAction,ViewAction和其他 - 是一个好方法。试试看看你的代码变得多少。

当你有一个工厂方法需要一个像“View”这样的字符串并返回一个Action,并且你调用它时,你已经隔离了你的条件。那很棒。你不能正确地欣赏力量,直到你尝试了它 - 所以给它一个镜头!

7

有几件事情要考虑:

  • 你只实例化对象的每一次。一旦你这样做了,就不需要关于它的类型的更多条件。

  • 即使在一次性实例中,如果您使用了子类,您将删除多少条件?使用条件语句像这样的代码很容易被完全相同的条件一次又一次又一次的 ...

  • 当你需要在未来foo行动值会发生什么?你需要修改多少个地方?

  • 如果您需要的barfoo稍有不同,该怎么办?通过课程,您只需从FooAction继承BarAction,覆盖您需要更改的一件事。

从长远看面向对象的代码通常比程序代码更易于维护 - 大师们不会有任何问题,但对于我们其余的人有的差异。

+0

我可以看到下游的好处。但是,我仍然需要一个开关箱来决定创建哪种类型。 – Charles 2011-02-01 20:11:30

3

你的例子并不需要多态,它可能不会被建议。尽管用多态调度替换条件逻辑的原意是正确的。

下面是区别:在你的例子中,你有一个小的固定(和预先确定)的行动。此外,“排序”和“编辑”行为几乎没有什么共同之处,这些行为并没有强烈关联。多态性过度构建您的解决方案。另一方面,如果你有很多具有特殊行为的对象,那么对于一个共同的概念,多态性就是你想要的。例如,在游戏中可能有很多玩家可以“激活”的对象,但每个对象的反应都不相同。你可以用复杂的条件来实现这个(或者更可能是switch语句),但是多态会更好。多态性允许您引入不属于您原始设计的新对象和行为(但符合其精神)。

在你的例子中,对于支持视图/编辑/排序动作的对象进行抽象仍然是一个好主意,但也许不会自己抽象这些动作。这是一个测试:你是否想要把这些行为放在一个集合中?可能不是,但你可能有一个支持它们的对象列表。

3

有几种方法可以将输入字符串转换为给定类型的对象,而条件语句绝对是其中之一。根据实现语言的不同,还可以使用switch语句,该语句允许将期望的字符串指定为索引,并创建或获取相应类型的对象。仍然有更好的方法来做到这一点。

的查找表可以被用来输入串映射到所需的对象:

action = table.lookup (action_name); // Retrieve an action by its name 
if (action == null) ...    // No matching action is found 

初始化代码将需要创建所需的对象的护理,例如

table ["edit"] = new EditAction(); 
table ["view"] = new ViewAction(); 
... 

这是可以扩展以涵盖更多细节的基本方案,例如操作对象的附加参数,在使用它们进行表查找之前对操作名称进行规范化,使用整数而不是字符串来替换具有纯数组的表,以标识请求的操作等

1
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(); 

所以我不需要条件在这里,但我仍然需要它来决定首先实例化哪个BaseAction类型。没有办法完全摆脱条件。

0

多态性是一种绑定方法。这是一种称为“对象模型”的特例。对象模型用于操纵复杂的系统,如电路或绘图。考虑存储/编组文本格式的东西:项目“A”,连接到项目“B”和“C”。现在你需要知道什么是连接到A.一个人可能会说,我不打算为此创建一个对象模型,因为我可以在解析时单数数据。在这种情况下,你可能是对的,你可能离开对象模型。但是如果你需要用进口设计做很多复杂的操作呢?你会以文本格式操纵它还是通过调用java方法发送消息,并且引用java对象更方便?这就是为什么有人提到你只需要翻译一次。

1

我一直在想这个问题可能比我遇到的其他开发者更多。他们中的大多数完全不知道维护长嵌套if-else语句或切换案例的成本。我完全理解你的问题,在你的案例中应用名为“用多态性替代条件”的解决方案。只要已经选择了对象,您就会成功注意到多态性的作用。在这个阶段也有人说,这个问题可以归结为关键[关键] - > [类]。这里是例如解决方案的AS3实现。

private var _mapping:Dictionary; 
private function map():void 
{ 
    _mapping["view"] = new ViewAction(); 
    _mapping["edit"] = new EditAction(); 
    _mapping["sort"] = new SortAction(); 
} 

private function getAction(key:String):BaseAction 
{ 
    return _mapping[key] as BaseAction; 
} 

运行,将你喜欢:

public function run(action:String):void 
{ 
    var selectedAction:BaseAction = _mapping[action]; 
    selectedAction.apply(); 
} 

在ActionScript3中有一种叫做getDefinitionByName全局函数(键:字符串)类。这个想法是使用您的键值来匹配表示解决方案的类的名称与您的条件。在你的情况下,您需要将“视图”更改为“ViewAction”,“编辑”更改为“EditAction”,然后“排序”更改为“SortAtion”。无需使用查找表记住任何内容。该功能运行将是这样的:

public function run(action:Script):void 
{ 
    var class:Class = getDefintionByName(action); 
    var selectedAction:BaseAction = new class(); 
    selectedAction.apply(); 
} 

不幸的是你失去编译该解决方案检查,但你得到的灵活性,增加了新的行动。如果你创建一个新的密钥,你唯一需要做的就是创建一个适当的类来处理它。

即使你不同意我的意见,也请留下评论。

7

即使最后答案是一年前,我想作一些评论在这个题目/评论。

答案审查

我@CarlManaster同意关于编码switch声明一次,以避免所有处理重复代码的众所周知的问题,在这种情况下,涉及条件语句(其中一些由@thkala提及)。

我不相信通过@KonradSzałwiński或@AlexanderKogtenkov提出的做法符合这一情况的原因有两个:

首先,从你描述的问题,你并不需要动态改变之间的映射动作的名称和处理动作的实例。

注意这些解决方案允许这样做(通过简单地指定动作名称到一个新的操作实例),而静态的,基于交换的解决方案不(映射被硬编码)。 此外,如果不应该执行某个操作(switch语句的default部分),您仍然需要一个条件来检查映射表中是否定义了给定的键。

其次,在这个特殊的例子,dictionaries真的隐藏switch语句的实现。更重要的是,使用default子句读取/理解switch语句可能会更容易,而不是必须从思维上执行从映射表返回处理对象的代码,包括处理未定义的键。

有你就可以摆脱所有条件句的方式,包括switch语句:

卸下switch语句(不使用条件语句的话)

如何创建正确的行动目标行动名称?

我会在语言无关所以这个答案并没有得到长,但关键是要实现类是对象太

如果您已经定义了一个多态层次结构,那么引用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(); 
    } 
} 

那么,这是什么方法呢?

首先,检索这个的所有具体子类(它指向BaseAction)。在你的例子中,你会收到一个集合ViewAction,EditActionSortAction

请注意,我说具体的子类,不是所有的子类。如果层次更深,具体的子类将始终是层次结构(叶)底部的子类。那是因为他们是唯一不应该抽象并提供真正实施的人。

其次,获取第一个子类,它回答它是否可以通过名称处理动作(我使用的是lambda/closure风格的表示法)。该handlesByName类方法的ViewAction示例实现会是什么样子:

public static class ViewAction { 

    public static bool handlesByName(anActionName) { 
     return anActionName == 'view' 
    } 

} 

第三,我们发送新到处理的动作,有效地创建它的一个实例类的消息。

当然,你必须处理这个情况,即任何子类都没有通过它的名字来处理动作。包括Smalltalk和Ruby在内的许多编程语言允许将detect方法传递给第二个lambda/closure,只有在没有任何子类匹配条件时才会进行评估。 另外,你将不得不处理不止一个子类通过其名称处理动作的情况(可能这些方法之一是以错误的方式编码的)。

结论这种方法的

一个优势是,新的行动可以通过书面形式(而不是修改)现有代码的支持:只要创建的BaseAction一个新的子类,并正确地实现handlesByName类方法。它有效地支持通过添加新概念来添加新功能,而无需修改现有的功能。很显然,如果新功能需要将新的多态方法添加到层次结构中,则需要进行更改。另外,您可以使用您的系统反馈提供开发人员:“所提供的操作不由任何BaseAction的子类处理,请创建一个新的子类并实现抽象方法”。对我而言,模型本身告诉你什么是错的事实(而不是试图在思维上执行查找表)为增加价值和明确指示必须完成的工作提供了指导。

是的,这听起来可能是过度设计。请保持开放的态度,并意识到解决方案是否过度设计必须与您使用的特定编程语言的开发文化相结合。例如,.NET人可能不会使用它,因为.NET不允许您将类视为真实对象,而另一方面,该解决方案在Smalltalk/Ruby文化中使用。

最后,使用常识和口味来事先确定一个特定的技术是否真正解决了您的问题,然后再使用它。这很诱人,但是应该评估所有的权衡(文化,开发者的资历,抗拒变化,开放的态度等)。

0

您可以将字符串和相应的操作类型存储在哈希映射中的某处。

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; 

theAction = actionMap.get(action); // decide at runtime, no conditions 
theAction.doSomething(); 
相关问题