为什么可以使用多态来替换switch或else-if语句?

时间:2013-10-03 11:38:47

标签: c++ polymorphism switch-statement

我对帖子中的内容感到有些困惑:case-vs-if-else-if-which-is-more-efficient

多次建议使用多态来替换long case / if-else语句。我试图了解真正的意义。你怎么能取代:

case TASK_A:
    // do things for task A
    break;
case TASK_B:
    // do things for task B
    break;
        :
        :
case TASK_J:
    // do things for task J
    break;

有多态吗?如果“do ...”部分基本上是相同的重复,我可以理解它,但如果某些或所有“案例”之间存在显着差异,那么这仍然适用吗?

6 个答案:

答案 0 :(得分:4)

您可以创建父类/接口,例如task定义子类覆盖的函数(可能是抽象的);我们称这个函数为handle_task

然后,您为每种类型的任务(即上面的每个case语句)创建一个子类,并将// do things for task X放入该类的handle_task实现

由于多态性,这些子类中的每一个都可以作为父类的实例传递/处理,当您对它们调用handle_task时,将执行正确的代码。

一个快速工作的例子:

#include <iostream>

class Task {
  public:
    virtual void handle_task()
    {
      std::cout << "Parent task" << std::endl;
    }
};

class Task_A: public Task {
  public:
    void handle_task()
    {
      std::cout << "task a" << std::endl;
    }
};

class Task_B: public Task {
  public:
    void handle_task()
    {
      std::cout << "task b" << std::endl;
    }
};

int main( void )
{
  Task *task;
  Task_A a;
  Task_B b;

  task=&a;
  task->handle_task();
  task=&b;
  task->handle_task();

}

将打印

/tmp$ g++ test.cpp
/tmp$ ./a.out
task a
task b

答案 1 :(得分:4)

在您链接到的示例中,switch位于对象的类型之上,建议使用多态来消除检查类型的需要。也就是说,在基类中声明一个虚函数,为每个具体类重写它以执行任何需要的操作,并用对该函数的调用替换整个switch

在您的情况下,您正在测试变量的值,而不是对象的类型。但是,如果您愿意,可以将其转换为多态解决方案:

struct Task         {virtual void do_things() = 0;};
struct TaskA : Task {virtual void do_things() {/*do things for task A*/}};
struct TaskB : Task {virtual void do_things() {/*do things for task B*/}};
//...
struct TaskJ : Task {virtual void do_things() {/*do things for task J*/}};

然后你可以用一个(智能)指针替换你正在切换的变量Task;并使用task->do_things()切换。这是否比switch更好是一个品味问题。

答案 2 :(得分:3)

主要设计原因是多态性允许您在不触及公共代码的情况下解耦代码并对其进行扩展。一个额外的效率原因是您不需要通过可能的代码路径进行线性搜索,而是无条件地跳转到所需的操作(尽管这是一个实现细节)。

这是一个纯C版的多态性,可能会出错:

// Switch-based:

void do_something(int action, void * data)
{
    switch(action)
    {
        case 1: foo(data); break;
        case 2: bar(data); break;
        case 3: zip(data); break;
        default: break;
    }
}

// Polymorphic:

typedef void (*action_func)(void *);

void do_something(action_func f, void * data)
{
    f(data);
}

如您所见,第二个版本更易于阅读和维护,如果您想添加新操作,则无需触及。

答案 3 :(得分:2)

重要的一点是脱钩。在上面的代码中,您需要知道存在哪些情况。你必须每次列出所有这些。如果将逻辑从switch分支放入虚方法,则调用代码不再需要

  • 实际存在的案例和
  • 每个案例的逻辑是什么。

相反,逻辑放在它所属的位置 - 在类中。

现在考虑添加另一个案例。在您的代码中,您必须触摸程序中的每个位置,其中使用了这样的switch语句。不仅你必须找到它们(不要忽视任何!),它们甚至可能不在你自己的代码中,因为你正在为某些其他人使用的libarry编写代码。使用虚拟方法,您只需在新类中根据需要覆盖一些方法,一切都将立即生效。

  BaseTask = class
  {
    virtual void Perform() = 0;
  }

  TaskOne = class(BaseTask)
  {
    void Perform() { cout << "Formatting hard disk ..."; }
  }

  TaskTwo = class(BaseTask)
  {
    void Perform() { cout << "Replacing files with random content ..."; }
  }

所以现在调用代码只需做

foreach( BaseTask task in Tasks)  // pseudo code    
{
  task.Perform();
}

现在让我们假设您添加了另一项任务:

  TaskThree = class(BaseTask)
  {
    void Perform() { cout << "Restoring everything form the backup..."; }
  }

你已经完成了。没有开关编辑,没有添加案例。这有多酷?

答案 4 :(得分:0)

上课:Animal作为2个子类:DogBird

如果你在Dog或Bird上调用它,你可以实现feed()函数。

而不是制作这个:

if object is dog
    object.dog.feed()
else
    object.bird.feed()

你这样做:

object.feed()

答案 5 :(得分:0)

您基本上有一个基类Base,其中包含纯虚拟成员函数do_task()。然后,您从DerivedA继承DerivedADerivedJ .. Base,他们都定义了自己的do_task版本。然后你只需致电:

std::shared_ptr<Base> obj = // points to a Derivedx

// ....

obj->do_task()