Ventajas de múltiples métodos sobre Switch

12

Hoy recibí una revisión del código de un desarrollador senior que preguntaba: "Por cierto, ¿cuál es tu objeción al envío de funciones a través de una declaración de cambio?" He leído en muchos lugares acerca de cómo impulsar un argumento a través del cambio a los métodos de llamada es un OOP malo, no es extensible, etc. Sin embargo, no puedo encontrar una respuesta definitiva para él. Me gustaría resolver esto por mí mismo de una vez por todas.

Aquí están nuestras sugerencias de código de la competencia (php se usa como ejemplo, pero se puede aplicar de manera más universal):

class Switch {
   public function go($arg) {
      switch ($arg) {
         case "one":
            echo "one\n";
         break;
         case "two":
            echo "two\n";
         break;
         case "three":
            echo "three\n";
         break;
         default:
            throw new Exception("Unknown call: $arg");
         break;
      }
   }
}

class Oop {
   public function go_one() {
      echo "one\n";
   }
   public function go_two() {
      echo "two\n";
   }
   public function go_three() {
      echo "three\n";
   }
   public function __call($_, $__) {
      throw new Exception("Unknown call $_ with arguments: " . print_r($__, true));
   }
}

Parte de su argumento fue que "(el método de cambio) tiene una forma mucho más limpia de manejar los casos predeterminados que el método genérico __call ()"

No estoy de acuerdo con la limpieza y, de hecho, prefiero llamar, pero me gustaría escuchar lo que otros tienen que decir.

Argumentos que puedo encontrar para apoyar el esquema Oop :

  • Un poco más limpio en términos del código que debe escribir (menos, más fácil de leer, menos palabras clave a considerar)
  • No todas las acciones delegadas a un solo método. No hay mucha diferencia en la ejecución aquí, pero al menos el texto está más compartimentado.
  • En la misma línea, se puede agregar otro método en cualquier lugar de la clase en lugar de un lugar específico.
  • Los métodos tienen espacios de nombre, lo cual es bueno.
  • No se aplica aquí, pero considere un caso donde Switch::go() operó en un miembro en lugar de en un parámetro. Tendría que cambiar el miembro primero, luego llamar al método. Para Oop puede llamar a los métodos de forma independiente en cualquier momento.

Argumentos que puedo encontrar para apoyar el esquema Switch :

  • En aras del argumento, un método más limpio para tratar una solicitud predeterminada (desconocida)
  • Parece menos mágico, lo que podría hacer que los desarrolladores desconocidos se sientan más cómodos

¿Alguien tiene algo que agregar para cada lado? Me gustaría tener una buena respuesta para él.

    
pregunta Explosion Pills 21.06.2011 - 15:36

5 respuestas

10

Un cambio no se considera OOP porque a menudo polimorfismo puede hacer el truco.

En su caso, una implementación de OOP podría ser esta:

class Oop 
{
  protected $goer;

  public function __construct($goer)
  {
    $this->goer = $goer;
  }

  public function go()
  {
    return $this->goer->go();
  }
}

class Goer
{
  public function go()
  {
    //...
  }
}

class GoerA extends Goer
{
  public function go()
  {
    //...
  }
}

class GoerB extends Goer
{
  public function go()
  {
    //...
  }
}

class GoerC extends Goer
{
  public function go()
  {
    //...
  }
}


$oop = new Oop(new GoerB());
$oop->go();
    
respondido por el Ando 21.06.2011 - 18:34
8

para este ejemplo:

class Switch
{
    public function go($arg)
    {
        echo "$arg\n";
    }
}

Está bien, sólo estoy bromeando parcialmente aquí. El argumento a favor / en contra del uso de una instrucción de cambio no se puede hacer con fuerza con un ejemplo tan trivial, porque el lado de la POO depende de la semántica involucrada, no simplemente del mecanismo de envío .

Las declaraciones de cambio suelen ser una indicación de clases o clasificaciones faltantes, pero no necesariamente. A veces, una declaración de cambio es solo una declaración de cambio .

    
respondido por el Steven A. Lowe 21.06.2011 - 19:07
1

Quizás no sea una respuesta, pero en el caso del código que no es de conmutación, parece que esto sería una mejor coincidencia:

class Oop {
  /**
   * User calls $oop->go('one') then this function will determine if the class has a 
   * method 'go_one' and call that. If it doesn't, then you get your error.
   * 
   * Subclasses of Oop can either overwrite the existing methods or add new ones.
   */
  public function go($arg){

    if(is_callable(array($this, 'go_'. $arg))){
      return call_user_func(array($this, 'go_'. $arg));
    }

    throw new Exception("Unknown call: $arg");
  }

  public function go_one() {
    echo "one\n";
  }
  public function go_two() {
    echo "two\n";
  }
  public function go_three() {
    echo "three\n";
  }
}

Una gran parte del rompecabezas a evaluar es lo que sucede cuando necesitas crear NewSwitch o NewOop . ¿Sus programadores tienen que saltar a través de aros por un método u otro? ¿Qué sucede cuando cambian las reglas, etc.?

    
respondido por el Rob 21.06.2011 - 16:01
0

En lugar de simplemente poner su código de ejecución en funciones, implemente un patrón de comando completo y coloque cada uno de ellos en su propia clase que implementa una interfaz común. Este método le permite utilizar IOC / DI para conectar los diferentes "casos" de su clase y le permite agregar y eliminar casos de su código fácilmente a lo largo del tiempo. También le proporciona una buena cantidad de código que no viola los principios de programación SOLID.

    
respondido por el P. Roe 21.06.2011 - 19:38
0

Creo que el ejemplo es malo. Si hay una función go () que acepta el argumento $ donde, si es perfectamente válido usar el interruptor en la función. Tener una única función go () facilita la modificación del comportamiento de todos los go_where () scenarious. Además, conservará la interfaz de clase: si utiliza varios métodos, modificará la interfaz de la clase con cada nuevo destino.

En realidad, el interruptor no debe reemplazarse con un conjunto de métodos, sino con un conjunto de clases, esto es polimorfismo. Entonces el destino será manejado por cada subclase, y habrá un solo método go () para todos ellos. Reemplazar condicional con polimorfismo es una de las refactorizaciones básicas, descrita por Martin Fowler. Pero es posible que no necesite polimorfismo, y el cambio es el camino a seguir.

    
respondido por el Maxim Krizhanovsky 21.06.2011 - 16:52

Lea otras preguntas en las etiquetas