Zalety wielu metod w porównaniu z przełącznikiem

12

Otrzymałem dzisiaj recenzję kodu od starszego programisty z pytaniem: „A propos, jaki jest twój sprzeciw wobec wywoływania funkcji za pomocą instrukcji switch?” Czytałem już w wielu miejscach o tym, jak pompowanie argumentu za pomocą przełączania do metod wywoływania jest złym OOP, nie jest tak rozszerzalne itp. Jednak tak naprawdę nie mogę znaleźć ostatecznej odpowiedzi dla niego. Chciałbym załatwić to raz na zawsze.

Oto nasze sugestie dotyczące konkurującego kodu (php użyty jako przykład, ale może być stosowany bardziej uniwersalnie):

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

Część jego argumentacji brzmiał: „To (metoda zamiany) ma znacznie czystszy sposób obsługi domyślnych przypadków niż to, co masz w ogólnej magicznej metodzie __call ()”.

Nie zgadzam się co do czystości i faktycznie wolę połączenie, ale chciałbym usłyszeć, co mają do powiedzenia inni.

Argumenty, które mogę przedstawić na poparcie Oopschematu:

  • Trochę czystszy pod względem kodu, który musisz napisać (mniej, łatwiej czytać, mniej słów kluczowych do rozważenia)
  • Nie wszystkie działania przekazane do jednej metody. Nie ma tutaj dużej różnicy w wykonaniu, ale przynajmniej tekst jest bardziej podzielony na przedziały.
  • W tym samym duchu można dodać inną metodę w dowolnym miejscu w klasie zamiast w określonym miejscu.
  • Metody mają przestrzeń nazw, co jest miłe.
  • Nie ma tu zastosowania, ale rozważ przypadek, w którym Switch::go()operowany jest na elemencie, a nie na parametrze. Najpierw musisz zmienić członka, a następnie wywołać metodę. Ponieważ Oopmożesz wywoływać metody niezależnie w dowolnym momencie.

Argumenty, które mogę przedstawić na poparcie Switchschematu:

  • Dla uproszczenia, czystsza metoda radzenia sobie z domyślnym (nieznanym) żądaniem
  • Wydaje się mniej magiczny, co może sprawić, że nieznani programiści poczują się bardziej komfortowo

Czy ktoś ma coś do dodania po każdej ze stron? Chciałbym mieć dla niego dobrą odpowiedź.

Pigułki przeciwwybuchowe
źródło
@Justin Satyr Myślałem o tym, ale myślę, że to pytanie dotyczy bardziej konkretnie kodu i znalezienia optymalnego rozwiązania i dlatego jest odpowiednie dla przepływu stosu. I jak mówi @ yes123, więcej osób może odpowiedzieć tutaj.
__call jest zły. Całkowicie zabija wydajność i można go użyć do wywołania metody, która ma być prywatna dla osób dzwoniących z zewnątrz.
Ooppozwala mieć phpdoc do opisywania każdej metody, które mogą być analizowane przez niektóre IDE (np. NetBeans).
binaryLV
fluffycat.com/PHP-Design-Patterns/… .. najwyraźniej Switch nie jest aż tak wydajny. -1
@GordonM: Co jeśli dana klasa nie ma metod prywatnych?
JAB

Odpowiedzi:

10

Przełącznik uważany jest za inny niż OOP, ponieważ często polimorfizm może załatwić sprawę.

W twoim przypadku implementacja OOP może być następująca:

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
źródło
1
Polimorfizm jest prawidłową odpowiedzią. +1
Rein Henrichs
2
To dobrze, ale wadą jest nadmierna proliferacja kodu. Musisz mieć klasę dla każdej metody, a to więcej kodu, z którym trzeba sobie poradzić .. i więcej pamięci. Czy nie ma szczęśliwego medium?
Explosion Pills
8

dla tego przykładu:

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

OK, tylko częściowo żartuję. Argument za / przeciw użyciu instrukcji switch nie może być silnie sformułowany w tak trywialnym przykładzie, ponieważ strona OOP zależy od semantyki, a nie tylko mechanizmu wysyłania .

Instrukcje przełączania często wskazują na brakujące klasy lub klasyfikacje, ale niekoniecznie. Czasami instrukcja switch jest tylko instrukcją switch .

Steven A. Lowe
źródło
+1 za „semantykę, a nie mechanizmy”
Javier
1

Być może nie jest to odpowiedź, ale w przypadku kodu nieprzełączającego wydaje się, że byłoby to lepsze dopasowanie:

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

Dużą częścią układanki do oceny jest to, co dzieje się, gdy trzeba utworzyć NewSwitchlub NewOop. Czy twoi programiści muszą skakać przez obręcze jedną czy drugą metodą? Co się stanie, gdy zmienią się twoje zasady itp.

Obrabować
źródło
Podoba mi się twoja odpowiedź - zamierzałem napisać to samo, ale zostałeś przez ciebie ninja'y. Myślę, że powinieneś zmienić method_exists na is_callable (), aby uniknąć problemów z odziedziczonymi chronionymi metodami i możesz zmienić call_user_func na $ this -> {'go _'. $ Arg} (), aby twój kod był bardziej czytelny. Inna sprawa - maby dodać komentarz, dlaczego magiczna metoda __call jest zła - niszczy ona funkcję is_callable () na tym obiekcie lub jego instancji, ponieważ zawsze zwróci PRAWDA.
Zaktualizowałem is_callable, ale opuściłem call_user_func, ponieważ (nie jest częścią tego pytania), mogą istnieć inne argumenty do przekazania. Ale teraz, kiedy to przeszło na programistów, zdecydowanie zgadzam się, że odpowiedź @ Andrei jest znacznie lepsza :)
Rob
0

Zamiast po prostu wstawiać kod wykonawczy do funkcji, zaimplementuj pełny wzorzec poleceń i umieść każdy z nich we własnej klasie, która implementuje wspólny interfejs. Ta metoda pozwala używać IOC / DI do łączenia różnych „przypadków” w klasie i pozwala z łatwością dodawać i usuwać przypadki z kodu w miarę upływu czasu. Daje także sporo kodu, który nie narusza zasad programowania SOLID.

P. Roe
źródło
0

Myślę, że przykład jest zły. Jeśli istnieje funkcja go (), która akceptuje argument $ gdzie, jeśli jest całkowicie poprawne, użyj przełącznika w funkcji. Posiadanie pojedynczej funkcji go () ułatwia modyfikowanie zachowania wszystkich scenariuszy go_where (). Dodatkowo zachowasz interfejs klasy - jeśli używasz różnych metod, modyfikujesz interfejs klasy z każdym nowym miejscem docelowym.

Właściwie przełącznika nie należy zastępować zestawem metod, ale zestawem klas - jest to polimorfizm. Następnie miejsce docelowe będzie obsługiwane przez każdą podklasę, a dla wszystkich będzie jedna metoda go (). Zastąpienie warunkowe polimorfizmem jest jednym z podstawowych refaktoryzacji opisanych przez Martina Fowlera. Ale możesz nie potrzebować polimorfizmu, a zmiana jest właściwą drogą.

Maxim Krizhanovsky
źródło
Jeśli nie zmienisz interfejsu, a podklasa ma własną implementację go(), może to łatwo naruszyć zasadę substytucji Liskowa. Jeśli dodajesz więcej funkcjonalności go(), ty nie chcesz zmienić interfejs miarę Jestem zainteresowaną.
Pills Explosion Pills