Vantagens para vários métodos sobre o switch

12

Recebi uma revisão de código de um desenvolvedor sênior hoje perguntando "A propósito, qual é a sua objeção a enviar funções por meio de uma declaração de switch?" Eu já li em muitos lugares sobre como bombear um argumento através da opção de alternar para chamar métodos é ruim OOP, não tão extensível, etc. No entanto, não consigo realmente encontrar uma resposta definitiva para ele. Eu gostaria de resolver isso para mim de uma vez por todas.

Aqui estão nossas sugestões de códigos concorrentes (php usado como exemplo, mas pode ser aplicado de maneira mais 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 seu argumento era "Ele (alternar método) tem uma maneira muito mais limpa de lidar com casos padrão do que o que você possui no método mágico genérico __call ()".

Discordo da limpeza e, de fato, prefiro telefonar, mas gostaria de ouvir o que os outros têm a dizer.

Argumentos que posso apresentar em apoio ao Oopesquema:

  • Um pouco mais limpo em termos do código que você precisa escrever (menos, mais fácil de ler, menos palavras-chave a serem consideradas)
  • Nem todas as ações delegadas a um único método. Não há muita diferença na execução aqui, mas pelo menos o texto é mais compartimentado.
  • Na mesma linha, outro método pode ser adicionado em qualquer lugar da classe, em vez de em um local específico.
  • Os métodos são namespaced, o que é bom.
  • Não se aplica aqui, mas considere um caso em que Switch::go()operado em um membro em vez de um parâmetro. Você precisaria alterar o membro primeiro e depois chamar o método Pois Oopvocê pode chamar os métodos de forma independente a qualquer momento.

Argumentos que posso apresentar em apoio ao Switchesquema:

  • Por uma questão de argumento, método mais limpo de lidar com uma solicitação padrão (desconhecida)
  • Parece menos mágico, o que pode fazer com que desenvolvedores desconhecidos se sintam mais confortáveis

Alguém tem algo a acrescentar para ambos os lados? Eu gostaria de ter uma boa resposta para ele.

Comprimidos de explosão
fonte
@ Justin Satyr Pensei nisso, mas acho que essa pergunta é mais específica sobre código e como encontrar uma solução ideal e, portanto, apropriada para o fluxo de pilha. E como o @ yes123 diz, é provável que mais pessoas respondam aqui.
__call é ruim. Isso mata completamente o desempenho e você pode usá-lo para chamar um método que deveria ser privado para chamadores externos.
Ooppermite ter phpdoc para descrever cada método, que pode ser analisado por alguns IDEs (por exemplo, NetBeans).
precisa saber é o seguinte
fluffycat.com/PHP-Design-Patterns/… .. aparentemente Switch não é tão eficiente assim. -1
@GordonM: E se a classe em questão não tiver métodos particulares?
JAB

Respostas:

10

Uma mudança é considerada não POO, porque muitas vezes o polimorfismo pode fazer o truque.

No seu caso, uma implementação OOP poderia 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
fonte
1
Polimorfismo é a resposta correta. 1
Rein Henrichs
2
Isso é bom, mas o lado negativo é uma proliferação excessiva de código. Você precisa ter uma classe para cada método e isso é mais código para lidar com .. e mais memória. Não existe um meio feliz?
Explosion Pills
8

para este exemplo:

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

OK, apenas brincando aqui. O argumento a favor / contra o uso de uma instrução switch não pode ser fortemente formulado com um exemplo tão trivial, porque o lado OOP depende da semântica envolvida, não apenas do mecanismo de despacho .

As instruções de troca geralmente são uma indicação de classes ou classificações ausentes, mas não necessariamente. Às vezes, uma instrução switch é apenas uma instrução switch .

Steven A. Lowe
fonte
+1 para "semântica, não mecanismos"
Javier
1

Talvez não seja uma resposta, mas no caso do código não alternativo, parece que seria uma correspondência melhor:

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

Uma grande parte do quebra-cabeça para avaliar é o que acontece quando você precisa criar NewSwitchou NewOop. Seus programadores precisam passar por um método ou outro? O que acontece quando suas regras mudam etc.

Roubar
fonte
Eu gosto da sua resposta - ia postar o mesmo, mas fui ninja por você. Eu acho que você deve alterar method_exists para is_callable () para evitar problemas com métodos protegidos herdados e você pode alterar call_user_func para $ this -> {'go _'. $ Arg} () para tornar seu código mais legível. Outra coisa - maby adicione um comentário por que o método mágico __call é ruim - estraga a funcionalidade is_callable () nesse objeto ou instância dele, porque sempre retornará TRUE.
Atualizei para is_callable, mas deixei o call_user_func desde (não faz parte desta pergunta), pode haver outros argumentos a serem transmitidos. Mas agora que isso tenha se moveu para programadores, eu definitivamente concordo que @ resposta de Andrea é muito melhor :)
Rob
0

Em vez de apenas colocar seu código de execução em funções, implemente um padrão de comando completo e coloque cada um deles em sua própria classe que implementa uma interface comum. Esse método permite que você use o IOC / DI para conectar os diferentes "casos" da sua classe e permite adicionar e remover facilmente casos do seu código ao longo do tempo. Também fornece um bom código que não viola os princípios de programação do SOLID.

P. Roe
fonte
0

Eu acho que o exemplo é ruim. Se houver uma função go () que aceite o argumento $ where, se é perfeitamente válido usar switch na função. A função go () única facilita a modificação do comportamento de todos os cenários go_where (). Além disso, você preservará a interface da classe - se você usar vários métodos, estará modificando a interface da classe com cada novo destino.

Na verdade, mudar não deve ser substituído por um conjunto de métodos, mas por um conjunto de classes - isso é polimorfismo. Em seguida, o destino será tratado por cada subclasse e haverá um método go () único para todos eles. Substituir condicional por polimorfismo é uma das refatorações básicas, descritas por Martin Fowler. Mas você pode não precisar de polimorfismo, e mudar é o caminho a seguir.

Maxim Krizhanovsky
fonte
Se você não alterar a interface e uma subclasse tiver sua própria implementação go(), poderá facilmente violar o princípio da Substituição de Liskov. Se você está adicionando mais funcionalidade para go(), você não deseja alterar a interface, tanto quanto eu sou concerend.
Comprimidos de explosão