스위치를 통한 여러 방법의 장점


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) 나는 그것에 대해 생각했지만이 질문은 코드와 최적의 솔루션을 찾는 것에 관한 것이므로 스택 오버 플로우에 적합하다고 생각합니다. 그리고 @ yes123이 말했듯이 더 많은 사람들이 여기에 응답 할 것입니다.

__ 통화가 잘못되었습니다. 성능을 완전히 저하시키고 외부 호출자에게만 제공되는 메소드를 호출하는 데 사용할 수 있습니다.

Oop일부 IDE (예 : NetBeans)에 의해 구문 분석 될 수있는 각 메소드를 설명하기 위해 phpdoc을 가질 수 있습니다.
binaryLV

fluffycat.com/PHP-Design-Patterns/… .. 분명히 스위치는 그다지 효율적이지 않습니다. -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
Rein Henrichs

2
이것은 좋지만 단점은 코드의 과도한 확산입니다. 각 메소드에 대한 클래스가 있어야하며 .. 및 더 많은 메모리를 처리하기 위해 더 많은 코드가 필요합니다. 행복한 매체가 없습니까?
폭발 환약

8

이 예제의 경우 :

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

좋아, 부분적으로 농담이야. OOP 측은 디스패치 메커니즘뿐만 아니라 관련된 의미론에 의존 하기 때문에 switch 문 사용에 대한 인수는 사소한 예제로 강력하게 만들 수 없습니다 .

스위치 문은 종종 누락 된 클래스 나 분류를 나타내지 만 반드시 그런 것은 아닙니다. 때때로 switch 문은 switch 문일뿐입니다 .


"메커니즘이 아닌 의미 론적"+1
Javier

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

평가하는 퍼즐의 큰 부분은 당신이 만들 필요가있을 때 일어나는 것이다 NewSwitchNewOop. 프로그래머가 한 가지 방법으로 농구대를 뛰어 넘어야합니까? 규칙이 변경되면 어떻게됩니까?


나는 당신의 대답을 좋아합니다-같은 것을 게시하려고했지만 당신에 의해 닌자를 얻었습니다. 상속 된 보호 된 메소드의 문제를 피하기 위해 method_exists를 is_callable ()로 변경해야하고 코드를 더 읽기 쉽도록 call_user_func를 $ this-> { 'go _'. $ arg} ()로 변경할 수 있다고 생각합니다. 또 다른 방법-maby는 매직 메소드 __call이 나쁜 이유에 대한 설명을 추가합니다. 항상 TRUE를 반환하기 때문에 해당 객체 또는 인스턴스의 is_callable () 기능을 망칩니다.

로 업데이트 is_callable했지만 이후 (이 질문의 일부가 아닌) call_user_func를 그대로두면 전달해야 할 다른 인수가있을 수 있습니다. 그러나 이것이 프로그래머에게로 옮겨 졌기 때문에 나는 @Andrea의 대답이 훨씬 낫다는 것에 분명히 동의합니다.)
Rob

0

실행 코드를 함수에 넣는 대신 전체 명령 패턴을 구현하고 각 인터페이스를 공통 인터페이스를 구현하는 자체 클래스에 배치하십시오. 이 방법을 사용하면 IOC / DI를 사용하여 클래스의 다른 "케이스"를 연결하고 시간이 지남에 따라 코드에서 케이스를 쉽게 추가하고 제거 할 수 있습니다. 또한 SOLID 프로그래밍 주체를 위반하지 않는 코드를 제공합니다.


0

나는 그 예가 좋지 않다고 생각한다. $ where 인수를 허용하는 go () 함수가있는 경우 함수에서 switch를 사용하는 것이 완벽합니다. 단일 go () 함수를 사용하면 모든 go_where () 시나리오의 동작을보다 쉽게 ​​수정할 수 있습니다. 또한 클래스 인터페이스를 유지합니다. 다양한 방법을 사용하는 경우 새 대상마다 클래스의 인터페이스를 수정합니다.

실제로 스위치는 메소드 세트로 바꾸지 말고 클래스 세트로 바꾸어야합니다. 이것은 다형성입니다. 그런 다음 대상은 각 서브 클래스에 의해 처리되며 모든 대상에 대해 단일 go () 메소드가 있습니다. 조건부를 다형성으로 교체하는 것은 Martin Fowler가 설명한 기본 리팩토링 중 하나입니다. 그러나 다형성이 필요하지 않을 수도 있으며 스위치가 나아갈 길입니다.


인터페이스를 변경하지 않고 서브 클래스에 자체 구현이 go()있는 경우 Liskov 대체 원칙을 쉽게 위반할 수 있습니다. 에 더 많은 기능을 추가하는 경우 go(), 내가 생각 하는 한 인터페이스를 변경하고 싶습니다.
폭발 환약
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.