Why can polymorphism be used to replace switch or else-if statements?
我对我在文章中读到的内容有点困惑:案例与假设,如果哪个更有效率的话
很多时候建议使用多态性替换长case/if-else语句。我正努力让自己明白这真正的含义。如何替换:
1 2 3 4 5 6 7 8 9 10 11 | 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…"部分基本上是相同的重复,我可以理解它,但是如果部分或全部"cases"之间存在显著差异,那么这仍然适用吗?
在您链接到的示例中,
在您的例子中,您测试的是变量的值,而不是对象的类型。但是,如果需要,可以将其转换为多态性解决方案:
1 2 3 4 5 | 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*/}}; |
然后,您可以用一个指向
您创建一个父类/接口,例如
然后为每种类型的任务(即上面的每个
由于多态性,这些子类中的每一个都可以作为父类的实例进行传递/处理,当您对它们调用
一个快速工作的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #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(); } |
将打印
1 2 3 4 | /tmp$ g++ test.cpp /tmp$ ./a.out task a task b |
主要的设计原因是多态性允许您在不接触公共代码的情况下分离和扩展代码。另一个提高效率的原因是,您不需要通过可能的代码路径进行线性搜索,而是无条件地跳到所需的操作(尽管这是一个实现细节)。
这是一个纯C版本的多态性,可能会令人不快:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // 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); } |
如您所见,第二个版本更易于阅读和维护,如果您想添加新的操作,则无需触摸。
一个重要的点是脱钩。在上面的代码中,您需要知道存在哪些情况。你必须每一次都列出来。如果将开关分支的逻辑放入虚拟方法中,则调用代码不再需要
- 实际存在的案例以及
- 每个案例的逻辑是什么?
相反,逻辑被放在它所属的位置——在类中。
现在考虑添加另一个案例。在您的代码中,您必须触摸程序中的每一个地方,在那里使用这样的switch语句。不仅你必须找到它们(不要忽略任何!)它们甚至可能不在您自己的代码中,因为您正在为其他人使用的某种libarry编写代码。使用虚拟方法,您只需根据需要在新类中重写一些方法,一切都将立即工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 ..."; } } |
所以现在呼叫码只需要做
1 2 3 4 | foreach( BaseTask task in Tasks) // pseudo code { task.Perform(); } |
现在假设您添加了另一个任务:
1 2 3 4 | TaskThree = class(BaseTask) { void Perform() { cout <<"Restoring everything form the backup..."; } } |
你就完了。没有开关编辑,没有案例添加。这有多酷?
基本上,您有一个带有纯虚拟成员函数
1 2 3 4 5 | std::shared_ptr<Base> obj = // points to a Derivedx // .... obj->do_task() |
取一个类:
您实现了
不要这样做:
1 2 3 4 | if object is dog object.dog.feed() else object.bird.feed() |
你只是这样做:
1 | object.feed() |