c++ - son - qué tipo de declaraciones de cambio se presentan en el régimen cambiario
¿Por qué se puede usar el polimorfismo para reemplazar las declaraciones de cambio o de otro tipo? (6)
Básicamente tiene una base de clase Base
con una función de miembro virtual pura do_task()
. A continuación, hereda DerivedA
, DerivedA
.. DerivedJ
de Base
y todos ellos definen su propia versión de do_task
. Entonces simplemente llama:
std::shared_ptr<Base> obj = // points to a Derivedx
// ....
obj->do_task()
Estoy un poco confundido con lo que leo en la publicación: case-vs-if-else-if-which-is-more-efficient
Se sugiere muchas veces que las declaraciones largas de caso / si no deben reemplazarse con el uso de polimorfismo. Estoy tratando de entender lo que realmente significa. ¿Cómo puedes reemplazar?
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;
Con polimorfismo? Podría ordenarlo, entenderlo si la parte "do ..." es básicamente la misma repetición, pero si hay diferencias significativas entre algunos o todos los "casos", ¿esto sigue siendo válido?
En el ejemplo al que se vincula, el switch
está sobre el tipo de un objeto, y la sugerencia es usar polimorfismo para eliminar la necesidad de verificar el tipo. Es decir, declarar una función virtual en la clase base, anularla para cada clase concreta para hacer lo que sea necesario y reemplazar todo el switch
con una llamada a esa función.
En su caso, está probando el valor de una variable, no el tipo de objeto. Sin embargo, si lo desea, podría transformarlo en una solución polimórfica:
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*/}};
Luego puede reemplazar la variable que está cambiando con un puntero (inteligente) a Task
; y el cambio con la task->do_things()
. Si eso es mejor que un switch
es una cuestión de gusto.
La principal razón del diseño es que el polimorfismo le permite desacoplar el código y extenderlo sin tocar el código común. Una razón de eficiencia adicional es que no necesita realizar una búsqueda lineal a través de posibles rutas de código, sino que salta incondicionalmente a la acción deseada (aunque se trata de un detalle de implementación).
Aquí hay una versión en C pura del polimorfismo que puede ser ilustrada:
// 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);
}
Como puede ver, la segunda versión es más fácil de leer y mantener, y no necesita ser tocada si desea agregar nuevas acciones.
Tome una clase: Animal
que como 2 subclase: Dog
y Bird
Implementa la función feed()
que es diferente si la llama en Dog o en Bird.
En lugar de hacer esto:
if object is dog
object.dog.feed()
else
object.bird.feed()
Simplemente lo haces:
object.feed()
Un punto importante es el desacoplamiento. En su código anterior necesita saber qué casos existen. Debes enumerarlos todos a la vez. Si coloca la lógica de las ramas de los conmutadores en métodos virtuales, el código de llamada ya no necesita
- qué casos existen realmente y
- cuál es la lógica para cada caso
En cambio, la lógica se ubica en el lugar al que pertenece: en la clase.
Ahora piensa en agregar otro caso. En su código, debería tocar cada lugar del programa, donde se utiliza dicha instrucción de cambio. No solo debe ubicarlos (¡no pase por alto ninguno!), Incluso puede que no estén en su propio código, ya que está escribiendo código para algún tipo de libarry que usan otras personas. Con los métodos virtuales, simplemente anula algunos métodos según sea necesario en la nueva clase y todo funcionará inmediatamente.
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 ..."; }
}
Entonces, ahora el código de llamada solo tiene que hacer
foreach( BaseTask task in Tasks) // pseudo code
{
task.Perform();
}
Y ahora supongamos que agrega otra tarea:
TaskThree = class(BaseTask)
{
void Perform() { cout << "Restoring everything form the backup..."; }
}
Y tu estas listo. Sin cambios de edición, sin agregar casos. ¿Cuan genial es eso?
Usted crea una clase / interfaz principal, por ejemplo, una task
que define una función (potencialmente abstracta) que las clases secundarias anulan; vamos a llamar a esta función handle_task
A continuación, crea una clase secundaria para cada tipo de tarea (es decir, cada sentencia de case
anterior) y pone // do things for task X
en la implementación de handle_task
clases de handle_task
Debido al polimorfismo, cada una de estas clases secundarias se puede pasar como / tratadas como instancias de la clase principal, y cuando se llama a handle_task
en ellas, se ejecutará el código correcto.
Un ejemplo rápido trabajado:
#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();
}
Se imprimirá
/tmp$ g++ test.cpp
/tmp$ ./a.out
task a
task b