Преимущества нескольких методов перед переключением

12

Сегодня я получил обзор кода от старшего разработчика, спрашивающего: «Кстати, что ты возражаешь против диспетчеризации функций с помощью оператора switch?» Во многих местах я читал о том, что перекачивание аргумента через метод switch для вызова методов является плохим ООП, не настолько расширяемым и т. Д. Однако я не могу придумать для него однозначного ответа. Я хотел бы решить это для себя раз и навсегда.

Вот наши конкурирующие предложения кода (php используется в качестве примера, но может применяться более универсально):

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

Часть его аргумента звучала так: «Он (метод switch) имеет гораздо более чистый способ обработки случаев по умолчанию, чем тот, который вы используете в общем магическом методе __call ()».

Я не согласен с чистотой и на самом деле предпочитаю звонить, но я хотел бы услышать, что говорят другие.

Аргументы, которые я могу привести в поддержку Oopсхемы:

  • Немного чище с точки зрения кода, который вы должны написать (меньше, легче читать, меньше ключевых слов для рассмотрения)
  • Не все действия делегируются одному методу. Здесь нет большой разницы в исполнении, но, по крайней мере, текст более разделен.
  • В том же духе, другой метод может быть добавлен в любом месте класса вместо определенного места.
  • Методы пространства имен, что приятно.
  • Здесь не применимо, но рассмотрим случай, когда Switch::go()оперируется членом, а не параметром. Сначала вам нужно изменить член, а затем вызвать метод. Ибо Oopвы можете вызывать методы самостоятельно в любое время.

Аргументы, которые я могу привести в поддержку Switchсхемы:

  • Ради аргумента, более чистый метод работы с запросом по умолчанию (неизвестно)
  • Кажется менее волшебным, что может заставить незнакомых разработчиков чувствовать себя более комфортно

Кому-нибудь есть что добавить для любой из сторон? Я хотел бы получить хороший ответ для него.

Таблетки взрыва
источник
@Justin Satyr Я думал об этом, но думаю, что этот вопрос более конкретно касается кода и поиска оптимального решения и, таким образом, подходит для стекового потока. И как говорит @ yes123, больше людей, вероятно, ответят здесь.
__звонить плохо. Это полностью убивает производительность, и вы можете использовать его для вызова метода, который должен быть закрытым для внешних абонентов.
Oopпозволяет иметь phpdoc для описания каждого метода, который может быть проанализирован некоторыми IDE (например, NetBeans).
binaryLV
fluffycat.com/PHP-Design-Patterns/… .. очевидно, Switch не так уж и эффективен. -1
@GordonM: Что делать, если у рассматриваемого класса нет частных методов?
JAB

Ответы:

10

Переключатель считается не ООП, потому что часто полиморфизм может помочь.

В вашем случае реализация ООП может быть такой:

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
источник
1
Полиморфизм - это правильный ответ. +1
Рейн Хенрикс
2
Это хорошо, но недостатком является чрезмерное распространение кода. Вы должны иметь класс для каждого метода, и это больше кода для работы .. и больше памяти. Разве нет счастливой среды?
Взрыв таблетки
8

для этого примера:

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

ОК, только частично шучу здесь. Аргумент за / против использования оператора switch нельзя убедительно привести в таком тривиальном примере, поскольку сторона ООП зависит от используемой семантики, а не только от механизма диспетчеризации .

Операторы Switch часто указывают на отсутствующие классы или классификации, но не обязательно. Иногда оператор switch является просто оператором switch .

Стивен А. Лоу
источник
+1 за «семантику, а не механизмы»
Хавьер
1

Возможно, не ответ, но в случае кода без переключения, кажется, что это было бы лучшим соответствием:

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

Большая часть головоломки для оценки - это то, что происходит, когда вам нужно создать NewSwitchили NewOop. Ваши программисты должны прыгать через обручи тем или иным способом? Что происходит при изменении ваших правил и т. Д.

обкрадывать
источник
Мне нравится ваш ответ - собирался опубликовать то же самое, но получил от вас ниндзя. Я думаю, что вы должны изменить method_exists на is_callable (), чтобы избежать проблем с унаследованными защищенными методами, и вы можете изменить call_user_func на $ this -> {'go _'. $ Arg} (), чтобы сделать ваш код более читабельным. Другое дело - возможно, добавьте комментарий, почему магический метод __call плох - он разрушает функциональность is_callable () для этого объекта или его экземпляра, потому что он всегда будет возвращать TRUE.
Я обновил до is_callable, но оставил call_user_func, так как (не часть этого вопроса), могут быть другие аргументы для передачи. Но теперь, когда это перешло к программистам, я определенно согласен с тем, что @ Андреа ответ гораздо лучше :)
Роб
0

Вместо того, чтобы просто помещать исполняемый код в функции, реализуйте полный шаблон команды и поместите каждый из них в свой собственный класс, который реализует общий интерфейс. Этот метод позволяет вам использовать IOC / DI для соединения различных «падежей» вашего класса и позволяет легко добавлять и удалять наблюдения из вашего кода с течением времени. Это также дает вам хороший код, который не нарушает принципы программирования SOLID.

П. Роу
источник
0

Я думаю, что пример плохой. Если есть функция go (), которая принимает аргумент $ where, если совершенно правильно использовать switch в функции. Наличие единственной функции go () облегчает изменение поведения всех go_where () в зависимости от ситуации. Кроме того, вы сохраните интерфейс класса - если вы используете различные методы, вы изменяете интерфейс класса с каждым новым назначением.

На самом деле ключ должен быть заменен не набором методов, а набором классов - это полиморфизм. Тогда назначение будет обрабатываться каждым подклассом, и для всех них будет один метод go (). Замена условного полиморфизма является одним из основных рефакторингов, описанных Мартином Фаулером. Но вам может не понадобиться полиморфизм, и переход - это путь.

Максим Крыжановский
источник
Если вы не меняете интерфейс и у подкласса есть своя реализация, go()это может легко нарушить принцип подстановки Лискова. Если вы хотите добавить больше функциональных возможностей go(), вы действительно хотите изменить интерфейс, насколько я concerend.
Взрыв таблетки