Переваги декількох методів через комутатор


12

Сьогодні я отримав огляд коду від старшого розробника, запитуючи "До речі, яке ваше заперечення щодо диспетчеризації функцій за допомогою заяви переключення?" Я читав у багатьох місцях про те, як перекачування аргументу через перемикання на методи виклику є поганим OOP, не настільки розширюваним і т. Д. Однак я не можу дійсно придумати для нього остаточну відповідь. Я хотів би вирішити це для себе раз і назавжди.

Ось наші конкуруючі пропозиції щодо коду (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));
   }
}

Частина його аргументу полягала в тому, що "Це (метод переключення) має набагато більш чистий спосіб вирішення випадків за замовчуванням, ніж те, що є у загальному магічному методі __call ()."

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

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

  • Трохи більш чіткий з точки зору коду, який потрібно написати (менше, легше читати, менше ключових слів враховувати)
  • Не всі дії делеговані одному методу. Тут немає великої різниці у виконанні, але принаймні текст більш деталізований.
  • У цьому ж ключі інший метод може бути доданий будь-де в класі замість конкретного місця.
  • Методи розділені на імена, що приємно.
  • Тут не застосовується, але розглянемо випадок, коли Switch::go()керується членом, а не параметром. Вам слід спочатку змінити учасника, а потім викликати метод. Бо Oopви можете викликати методи самостійно в будь-який час.

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

  • Заради аргументу, більш чистий метод розгляду запиту за замовчуванням (невідомо)
  • Здається менш магічним, що може змусити незнайомих розробників відчувати себе більш комфортно

У когось є що додати для будь-якої сторони? Я хотів би отримати гарну відповідь за нього.


@Justin Satyr Я думав про це, але вважаю, що це питання більш конкретно стосується коду та пошуку оптимального рішення і, таким чином, підходить для stackoverflow. І як говорить @ yes123, більше людей, ймовірно, тут відповість.

__звонок поганий. Це повністю знищує продуктивність, і ви можете використовувати його для виклику методу, який повинен бути приватним для сторонніх абонентів.

Oopдозволяє мати phpdoc для опису кожного методу, який можна проаналізувати деякими IDE (наприклад, NetBeans).
binaryLV

fluffycat.com/PHP-Design-Patterns/… .. мабуть, Switch не все так ефективно. -1

@GordonM: Що робити, якщо у відповідного класу немає приватних методів?
JAB

Відповіді:


10

Перемикач вважається не OOP, оскільки часто поліморфізм може зробити свою справу.

У вашому випадку реалізація OOP може бути такою:

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

1
Поліморфізм - правильна відповідь. +1
Рейн Генріхс

2
Це добре, але недолік - це надмірне поширення коду. Ви повинні мати клас для кожного методу, і це більше коду для вирішення .. та більше пам’яті. Хіба немає щасливого середовища?
Вибухові таблетки

8

для цього прикладу:

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

Гаразд, лише частково жартую тут. Аргумент за / проти використання оператора перемикання не може бути сильно зроблений на такому тривіальному прикладі, оскільки сторона ООП залежить від семантики, що бере участь, а не лише від механізму відправки .

Оператори перемикання часто є ознакою відсутніх класів або класифікацій, але не обов'язково. Іноді оператор перемикання - це лише оператор перемикання .


+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} (), щоб зробити ваш код більш читабельним. Інша річ - maby додати коментар, чому магічний метод __call поганий - він руйнує функцію is_callable () на цьому об'єкті або його екземплярі, тому що він завжди повертатиме ІСТИНА.

Я оновив is_callable, але залишив call_user_func, оскільки (не є частиною цього питання), можуть бути передані інші аргументи. Але тепер, коли це перейшло до програмістів, я безумовно погоджуюся, що відповідь @ Андреа набагато краща :)
Роб

0

Замість того, щоб просто вводити свій виконуючий код у функції, реалізуйте повний шаблон команди і розмістіть кожну з них у своєму власному класі, який реалізує загальний інтерфейс. Цей метод дозволяє використовувати IOC / DI для з'єднання різних "справ" вашого класу та дозволяє легко додавати та вилучати випадки із свого коду з часом. Це також дає хороший біт коду, який не порушує принципів програмування SOLID.


0

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

Насправді перемикач не повинен бути замінений набором методів, а набором класів - це поліморфізм. Тоді призначення буде оброблятися кожним підкласом, і для них буде єдиний метод go (). Замінити умовно поліморфізмом є одним із основних рефактори, описаним Мартіном Фаулером. Але вам може не потрібен поліморфізм, і перемикання - це шлях.


Якщо не змінити інтерфейс і підклас має власну реалізацію, go()він може легко порушити принцип заміни Ліскова. Якщо ви хочете додати більше функціональних можливостей go(), ви дійсно хочете змінити інтерфейс, наскільки я concerend.
Вибухові таблетки
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.