여러 스위치 케이스로 애플리케이션을 리팩터링하는 방법은 무엇입니까?


10

정수를 입력으로 사용하고 입력을 기반으로 다른 클래스의 정적 메소드를 호출하는 응용 프로그램이 있습니다. 새로운 숫자가 추가 될 때마다 다른 케이스를 추가하고 다른 클래스의 다른 정적 메소드를 호출해야합니다. 이제 스위치에 50 개의 케이스가 있으며, 다른 케이스를 추가해야 할 때마다 나는 소리 쳤다. 더 좋은 방법이 있습니까?

나는 몇 가지 생각을 하고이 아이디어를 생각해 냈습니다. 전략 패턴을 사용합니다. 스위치 케이스 대신 키가 입력 정수인 전략 오브젝트 맵이 있습니다. 메소드가 호출되면 오브젝트를 찾고 오브젝트의 일반 메소드를 호출합니다. 이렇게하면 스위치 케이스 구조를 사용하지 않아도됩니다.

어떻게 생각해?


2
현재 코드의 실제 문제는 무엇입니까?
Philip Kendall

이러한 변경 중 하나를 수행해야 할 경우 어떻게됩니까? switch복잡한 시스템에서 사례 를 추가하고 기존 메소드를 호출해야합니까, 아니면 메소드와 호출을 모두 발명해야합니까?
Kilian Foth

@KilianFoth이 프로젝트를 유지 관리 개발자로 물려 받았으며 아직 변경하지 않아도됩니다. 그러나 곧 변경하겠습니다. 이제 리팩토링하고 싶습니다. 그러나 당신의 질문에 대답하기 위해, 후자에게 그렇습니다.
Kaushik Chakraborty

2
나는 당신이 무슨 일이 일어나고 있는지에 대한 간략한 예를 보여줄 필요가 있다고 생각합니다.
whatsisname

1
@KaushikChakraborty : 그런 다음 메모리에서 예제를 구성하십시오. 250 개 이상의 uber-switch 스위치가 적절한 상황이 있으며, 스위치 수에 관계없이 스위치가 나쁜 경우가 있습니다. 악마는 세부 사항에 있으며 우리는 세부 사항이 없습니다.
whatsisname

답변:


13

이제 스위치에 50 개의 케이스가 있으며, 다른 케이스를 추가해야 할 때마다 나는 소리 쳤다.

나는 다형성을 좋아합니다. 나는 SOLID를 좋아합니다. 나는 순수한 객체 지향 프로그래밍을 좋아합니다. 나는 그들이 도덕적으로 적용되기 때문에 나쁜 담당자가 주어진 것을 싫어.

전략에 대한 리팩토링에 대한 좋은 사례는 없습니다. 리팩토링에는 이름이 있습니다. 이를 조건부로 다형성 대체 라고 합니다.

c2.com 에서 적절한 조언을 찾았습니다 .

동일하거나 매우 유사한 조건부 테스트가 자주 반복되는 경우에만 의미가 있습니다. 단순하고 거의 반복되지 않는 테스트의 경우 간단한 조건부를 여러 클래스 정의의 세부 정보로 대체하고 실제로 조건부 필요한 활동이 필요한 코드에서이 모든 것을 옮기면 코드 난독 화의 교과서 예제가 생성됩니다. 독단적 순도보다 명확성을 선호합니다. -댄 뮬러

50 개의 케이스가있는 스위치가 있으며 50 개의 오브젝트를 생성하는 대안이 있습니다. 오와 50 줄의 객체 구성 코드. 이것은 진행되지 않습니다. 왜 안돼? 이 리팩토링은 숫자를 50에서 줄이는 데 도움이되지 않습니다. 다른 곳에서 동일한 입력에 다른 switch 문을 작성해야하는 경우이 리팩토링을 사용하십시오. 이 리팩토링이 100으로 다시 50으로 바뀌기 때문에 도움이됩니다.

당신이 가진 유일한 스위치 인 것처럼 "스위치"를 언급하는 한, 나는 이것을 권장하지 않습니다. 리팩토링을 통해 얻을 수있는 유일한 장점은 일부 goofball이 50 개의 케이스 스위치를 복사하여 붙여 넣을 가능성이 줄어든다는 것입니다.

내가 추천하는 것은 제외 할 수있는 공통성에 대해이 50 가지 사례를 면밀히 검토하는 것입니다. 나는 50을 의미합니까? 정말? 많은 경우가 필요하십니까? 당신은 여기에 많은 것을하려고 할 것입니다.


나는 당신이 말하는 것에 동의합니다. 코드에는 많은 중복성이 있으며 많은 경우가 필요하지는 않지만 커서 한 눈에 볼 수는 없습니다. 각 경우는 여러 시스템을 호출하고 결과를 집계하여 호출 코드로 리턴하는 메소드를 호출합니다. 모든 수업은 독립적이며, 하나의 직업을 수행하며, 높은 응집력 원칙을 위반할 것입니다.
Kaushik Chakraborty

2
나는 높은 응집력을 위반하지 않고 50을 얻을 수 있고 물건을 독립적으로 유지합니다. 하나의 숫자로는 할 수 없습니다. 2, 5, 5가 더 필요합니다. 그래서 그것이 팩토링 아웃이라고 불립니다. 진지하게, 전체 아키텍처를 살펴보고이 50 가지 사례에서 벗어날 수 없는지 살펴보십시오. 리팩토링은 잘못된 결정을 취소하는 것입니다. 그것들을 새로운 형태로 영속시키지 않습니다.
candied_orange

이제이 리팩토링을 사용하여 50을 줄이는 방법을 볼 수 있다면 좋습니다. Doc Browns 아이디어를 활용하려면 :지도 맵에는 두 개의 키가있을 수 있습니다. 생각할 것.
candied_orange

1
나는 Candied의 의견에 동의합니다. switch 문에서 문제는 50 가지가 아니라 문제는 상위 수준의 아키텍처 설계로, 50 가지 옵션 중에서 결정해야하는 함수를 호출하게합니다. 저는 매우 크고 복잡한 시스템을 설계했으며 그런 상황에 처한 적이 없습니다.
덩크

@Candied "다른 곳에서 동일한 입력에 다른 스위치 명령문을 작성해야하는 경우이 리팩토링을 사용합니다."정교하게 설명 할 수 있습니까? 스위치 케이스가있는 유사한 케이스가 있지만 먼저 승인, 검증, CRUD 절차를 수행 한 다음 dao를 수행하십시오. 따라서 모든 레이어에는 동일한 입력, 즉 정수와 같은 스위치 케이스가 있지만 auth, valid와 같은 다른 기능을 수행합니다. 그래서 우리는 다른 메소드를 가진 각 유형마다 하나의 클래스 전나무를 만들어야합니까? "동일한 입력에 동일한 스위치를 반복하여"우리의 사례가 말하는 것에 적합합니까?
Siddharth Trikha

9

코드의 일부 기능으로 초기화되는 전략 객체 맵만, 여기에는 여러 줄의 코드가 있습니다.

     myMap.Add(1,new Strategy1());
     myMap.Add(2,new Strategy2());
     myMap.Add(3,new Strategy3());

전략 개체는 모두 동일한 인터페이스를 구현해야하기 때문에 사용자와 동료가보다 균일 한 방식으로 별도의 클래스에서 호출되도록 함수 / 전략을 구현해야합니다. 이러한 코드는 종종보다 포괄적입니다.

     case 1:
          MyClass1.Doit1(someParameters);
          break;
     case 2:
          MyClass2.Doit2(someParameters);
          break;
     case 3:
          MyClass3.Doit3(someParameters);
          break;

그러나 새 번호를 추가해야 할 때마다이 코드 파일을 편집해야하는 부담에서 벗어나지 않습니다. 이 방법의 실제 이점은 다른 것입니다.

  • 맵의 초기화는 이제 특정 숫자와 관련된 함수 를 실제로 호출 하는 디스패치 코드와 분리되며 후자는 더 이상 50 반복을 포함 하지 않습니다myMap[number].DoIt(someParameters) . 따라서이 디스패치 코드는 새로운 번호가 도착할 때마다 만질 필요가 없으며 공개 폐쇄 원칙에 따라 구현 될 수 있습니다. 또한 디스패치 코드 자체를 확장해야하는 요구 사항이있는 경우 더 이상 50 개의 장소를 변경할 필요가 없으며 한 곳만 변경하면됩니다.

  • 맵의 내용은 런타임에 결정되며 (컴파일 타임 전에 스위치 구성의 내용이 결정되는 동안) 초기화 논리를보다 유연하거나 확장 가능하게 만들 수 있습니다.

그렇습니다. 몇 가지 장점이 있으며, 이는 더 많은 SOLID 코드를 향한 발걸음입니다. 그러나 리팩토링에 돈을 지불한다면 당신이나 당신 팀이 스스로 결정해야 할 것입니다. 디스패치 코드가 변경되지 않고 초기화 논리가 변경되며 가독성이 switch실제로 문제가되지 않으면 리팩토링이 그다지 중요하지 않을 수 있습니다.


모든 스위치를 다형성으로 맹목적으로 교체하는 것을 꺼려하지만 Doc Brown이 제안한 방식으로 맵을 사용하면 과거에 나에게 매우 효과적이라고 말할 것입니다. 같은 인터페이스를 구현하면 교체하십시오 Doit1, Doit2하나 개 등, Doit여러 가지 구현을 가지고하는 방법.
candied_orange

그리고 키로 사용 된 입력 심볼의 유형을 제어 할 수 있다면 입력 심볼 doTheThing()의 방법을 만들어 한 단계 더 나아갈 수 있습니다 . 그런 다음 맵을 유지할 필요가 없습니다.
Kevin Krumwiede

1
@KevinKrumwiede : 당신이 제안하는 것은 단순히 정수 대신에 전략 객체를 프로그램에서 전달하는 것을 의미합니다. 그러나 프로그램이 일부 외부 데이터 소스에서 입력으로 정수를 가져 오는 경우 최소한 시스템의 한 곳에서 정수에서 관련 전략으로의 맵핑 이 있어야합니다.
Doc Brown

Doc Brown의 제안에 따라 확장 : 전략 오브젝트를 작성하기위한 논리를 포함하는 팩토리를 작성할 수도 있습니다. 즉, CandiedOrange가 제공 한 답변이 가장 의미가 있습니다.
블라디미르 스토 키

@DocBrown "입력 심볼의 유형을 제어 할 수있는 경우"에서 얻은 내용입니다.
Kevin Krumwiede

0

@DocBrown의 답변에 요약 된 전략을 강력하게 선호합니다 .

나는 대답의 개선을 제안 할 것입니다.

전화

 myMap.Add(1,new Strategy1());
 myMap.Add(2,new Strategy2());
 myMap.Add(3,new Strategy3());

배포 할 수 있습니다. Open-Closed 원칙을 더 잘 준수하는 다른 전략을 추가하기 위해 동일한 파일로 돌아갈 필요가 없습니다.

Strategy1Strategy1.cpp 파일에서 구현한다고 가정 해보십시오 . 다음 코드 블록을 가질 수 있습니다.

namespace Strategy1_Impl
{
   struct Initializer
   {
      Initializer()
      {
         getMap().Add(1, new Strategy1());
      }
   };
}
using namespace Strategy1_Impl;

static Initializer initializer;

모든 StategyN.cpp 파일에서 동일한 코드를 반복 할 수 있습니다. 보시다시피, 그것은 반복되는 많은 코드가 될 것입니다. 코드 중복을 줄이려면 모든 Strategy클래스에서 액세스 할 수있는 파일에 넣을 수있는 템플릿을 사용할 수 있습니다 .

namespace StrategyHelper
{
   template <int N, typename StrategyType> struct Initializer
   {
      Initializer()
      {
         getMap().Add(N, new StrategyType());
      }
   };
}

그런 다음 Strategy1.cpp에서 사용해야하는 유일한 것은 다음과 같습니다.

static StrategyHelper::Initializer<1, Strategy1> initializer;

StrategyN.cpp의 해당 라인은 다음과 같습니다.

static StrategyHelper::Initializer<N, StrategyN> initializer;

구체적인 전략 클래스의 클래스 템플리트를 사용하여 템플리트를 다른 레벨로 사용할 수 있습니다.

class Strategy { ... };

template <int N> class ConcreteStrategy;

그런 다음 대신을 Strategy1사용하십시오 ConcreteStrategy<1>.

template <> class ConcreteStrategy<1> : public Strategy { ... };

Strategys를 등록하도록 헬퍼 클래스를 변경하십시오 .

namespace StrategyHelper
{
   template <int N> struct Initializer
   {
      Initializer()
      {
         getMap().Add(N, new ConcreteStrategy<N>());
      }
   };
}

Strateg1.cpp의 코드를 다음과 같이 변경하십시오.

static StrategyHelper::Initializer<1> initializer;

StrategN.cpp의 코드를 다음과 같이 변경하십시오.

static StrategyHelper::Initializer<N> initializer;
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.