Ventajas de múltiples métodos sobre el interruptor

12

Hoy recibí una revisión de código de un desarrollador sénior que preguntaba "Por cierto, ¿cuál es su objeción al envío de funciones mediante una declaración de cambio?" He leído en muchos lugares cómo bombear un argumento a través del cambio a métodos de llamada es una mala OOP, no tan extensible, etc. Sin embargo, realmente 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 (se usa php como ejemplo, pero puede aplicarse 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: "(el método de cambio) tiene una forma mucho más limpia de manejar los casos predeterminados que la que tiene en el método mágico 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 se me pueden ocurrir en apoyo del Oopesquema:

  • Un poco más limpio en términos del código que tiene que escribir (menos, más fácil de leer, menos palabras clave para considerar)
  • No todas las acciones se delegan a un solo método. No hay mucha diferencia en la ejecución aquí, pero al menos el texto está más compartimentado.
  • En el mismo sentido, 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 nombres, lo cual es bueno.
  • No se aplica aquí, pero considere un caso donde se Switch::go()opera en un miembro en lugar de un parámetro. Tendría que cambiar el miembro primero, luego llamar al método. Para Oopque pueda llamar a los métodos de forma independiente en cualquier momento.

Argumentos que se me pueden ocurrir en apoyo del Switchesquema:

  • En aras de la discusión, un método más limpio para tratar una solicitud predeterminada (desconocida)
  • Parece menos mágico, lo que puede hacer que los desarrolladores desconocidos se sientan más cómodos

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

Píldoras de explosión
fuente
@Justin Satyr Pensé en eso, pero creo que esta pregunta es más específica sobre el código y la búsqueda de una solución óptima y, por lo tanto, es apropiada para el stackoverflow. Y como dice @ yes123, es probable que más personas respondan aquí.
__la llamada es mala. Elimina completamente el rendimiento, y puede usarlo para llamar a un método que se supone que es privado para las personas que llaman desde afuera.
Ooppermite tener phpdoc para describir cada método, que algunos IDE pueden analizar (p. ej., NetBeans).
binaryLV
fluffycat.com/PHP-Design-Patterns/… .. aparentemente Switch no es tan eficiente. -1
@GordonM: ¿Qué pasa si la clase en cuestión no tiene métodos privados?
JAB

Respuestas:

10

Un cambio se considera no OOP porque a menudo el 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();
Ando
fuente
1
El polimorfismo es la respuesta correcta. +1
Rein Henrichs
2
Esto es bueno, pero la desventaja es una proliferación excesiva de código. Debe tener una clase para cada método y eso es más código para tratar ... y más memoria. ¿No hay un medio feliz?
Píldoras de explosión
8

para este ejemplo:

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

OK, solo bromeo aquí. El argumento a favor / en contra del uso de una declaración de cambio no puede hacerse con un ejemplo tan trivial, porque el lado de la POO depende de la semántica involucrada, no simplemente del mecanismo de despacho .

Las declaraciones de cambio son a menudo 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 .

Steven A. Lowe
fuente
+1 para "semántica, no mecanismos"
Javier
1

Quizás no sea una respuesta, pero en el caso del código sin interruptor, parece que esta 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 para evaluar es lo que sucede cuando necesitas crear NewSwitcho NewOop. ¿Tus programadores tienen que saltar a través de aros por un método u otro? Qué sucede cuando cambian tus reglas, etc.

Robar
fuente
Me gusta tu respuesta: iba a publicar lo mismo, pero te ninja. Creo que debe cambiar method_exists a is_callable () para evitar problemas con los métodos protegidos heredados y puede cambiar call_user_func a $ this -> {'go _'. $ Arg} () para que su código sea más legible. Otra cosa: puede agregar un comentario por qué el método mágico __call es malo: arruina la funcionalidad is_callable () en ese objeto o instancia de él porque siempre devolverá VERDADERO.
is_callableActualicé a , pero dejé call_user_func ya que (no es parte de esta pregunta), puede haber otros argumentos para transmitir. Pero ahora que esto se ha transferido a los programadores, definitivamente estoy de acuerdo en que la respuesta de @ Andrea es mucho mejor :)
Rob
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 implemente una interfaz común. Este método le permite usar IOC / DI para conectar los diferentes "casos" de su clase y le permite agregar y eliminar fácilmente casos de su código con el tiempo. También le proporciona un buen código que no viola los principios de programación SOLID.

P. Roe
fuente
0

Creo que el ejemplo es malo. Si hay una función go () que acepta el argumento $ where, si es perfectamente válido usar switch en la función. Tener una sola función go () facilita la modificación del comportamiento de todos los escenarios go_where (). Además, conservará la interfaz de la clase: si utiliza varios métodos, está modificando 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. Luego, el destino será manejado por cada subclase, y habrá un solo método go () para todos ellos. Reemplazar condicional por polimorfismo es una de las refactorizaciones básicas descritas por Martin Fowler. Pero es posible que no necesite polimorfismo, y cambiar es el camino a seguir.

Maxim Krizhanovsky
fuente
Si no cambia la interfaz y una subclase tiene su propia implementación go(), puede violar fácilmente el principio de sustitución de Liskov. Si va a añadir más funcionalidad a go(), usted no desea cambiar la interfaz por lo que yo soy concerend.
Píldoras de explosión