4

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

多次建议应该使用多态性来替换长的 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 ...”部分基本上是相同的重复,我可以理解它,但如果部分或全部“案例”之间存在显着差异,那么这仍然适用吗?

4

6 回答 6

5

在您链接到的示例中,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()。这是否比a更好switch是一个品味问题。

于 2013-10-03T11:48:25.287 回答
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
于 2013-10-03T11:43:32.250 回答
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);
}

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

于 2013-10-03T11:49:15.750 回答
2

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

  • 实际存在哪些案例以及
  • 每个案例的逻辑是什么。

相反,逻辑被放置在它所属的地方——在类中。

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

  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..."; }
  }

你完成了。没有开关编辑,没有添加案例。多么酷啊?

于 2013-10-03T11:45:23.957 回答
0

参加一个类:Animal作为 2 个子类:DogBird

feed()如果您在 Dog 或 Bird 上调用它,您将实现不同的功能。

而不是这样:

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

你只需这样做:

object.feed()
于 2013-10-03T11:46:19.067 回答
0

你基本上有一个Base带有纯虚成员函数的基类do_task()。然后,您继承DerivedA, DerivedA.. DerivedJfromBase并且它们都定义了自己的do_task. 然后,您只需调用:

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

// ....

obj->do_task()
于 2013-10-03T11:47:04.360 回答