중요한 분기없이 전략 패턴을 구현할 수 있습니까?


14

전략 패턴은 거대한 if ... else 구문을 피하고 기능을 쉽게 추가하거나 대체 할 수 있도록하는 데 효과적입니다. 그러나 여전히 내 의견으로는 하나의 결함이 남아 있습니다. 모든 구현에서 여전히 분기 구성이 필요한 것처럼 보입니다. 팩토리 또는 데이터 파일 일 수 있습니다. 예를 들어 주문 시스템을 사용하십시오.

공장:

// All of these classes implement OrderStrategy
switch (orderType) {
case NEW_ORDER: return new NewOrder();
case CANCELLATION: return new Cancellation();
case RETURN: return new Return();
}

이 이후의 코드는 걱정할 필요가 없으며 지금 새 주문 유형을 추가 할 곳은 단 하나 뿐이지 만이 코드 섹션은 여전히 ​​확장 할 수 없습니다. 데이터 파일로 가져 가면 가독성이 다소 높아집니다 (논쟁의 여지가 있음).

<strategies>
   <order type="NEW_ORDER">com.company.NewOrder</order>
   <order type="CANCELLATION">com.company.Cancellation</order>
   <order type="RETURN">com.company.Return</order>
</strategies>

그러나 이것은 여전히 ​​상용구 코드를 추가하여 데이터 파일을 처리하고, 더 쉽게 테스트 할 수 있고 상대적으로 안정적인 코드를 부여하지만 그럼에도 불구하고 추가 복잡성을 가중시킵니다.

또한 이러한 종류의 구문은 통합 테스트를 잘 수행하지 못합니다. 각각의 개별 전략은 지금 테스트하기가 더 쉬울 수 있지만 추가하는 모든 새로운 전략은 테스트하기가 복잡합니다. 패턴을 사용 하지 않았다면 가질 수있는 것보다 적지 만 여전히 존재합니다.

이 복잡성을 완화시키는 전략 패턴을 구현할 수있는 방법이 있습니까? 아니면 이것이 얻는 것만 큼 간단합니까? 더 나아가려고하면 이익이 거의 없거나 전혀없는 다른 추상화 계층 만 추가 할 것입니까?


흠 .... evalJava로 작동하지 않지만 다른 언어로는 작동하지 않을 수 있습니다.
FrustratedWithFormsDesigner

1
@FrustratedWithFormsDesigner reflection은 자바의 마법 단어입니다.
ratchet freak

2
여전히 어딘가에 그러한 조건이 필요합니다. 팩토리로 푸시하면 DRY를 준수하는 것입니다. 그렇지 않으면 if 또는 switch 문이 여러 곳에 나타날 수 있습니다.
Daniel B

1
언급 한 결함은 종종 공개 폐쇄 원칙 위반
k3b

관련 질문에 허용 된 답변 은 사전 /지도가 if-else 및 switch의 대안으로 제
안됨

답변:


16

당연히 아니지. IoC 컨테이너를 사용하더라도 주입 할 구체적인 구현을 결정하는 조건이 있어야합니다. 이것이 전략 패턴의 본질입니다.

사람들이 왜 이것이 문제라고 생각하는지 모르겠습니다. Fowler 's Refactoring 과 같은 일부 책 에는 다른 코드 중간에 스위치 / 케이스 또는 if / eles 체인이 보이면 그 냄새를 고려하여 자체 방법으로 옮기는 것이 좋습니다. 각 사례의 코드가 한 줄 이상인 경우 아마도 두 개 이상인 경우 전략을 반환하는 해당 메서드를 팩토리 메서드로 만드는 것을 고려해야합니다.

어떤 사람들은 이것을 스위치 / 케이스가 나쁘다는 의미로 사용했습니다. 그렇지 않다. 그러나 가능하면 자체적으로 상주해야합니다.


1
나도 그것을 나쁜 것으로 보지 않습니다. 그러나 나는 항상 코드를 만질 때 코드 양을 줄이는 방법을 찾고 있었기 때문에 문제가 발생했습니다.
Michael K

스위치 문 (및 긴 if / else-if 블록)은 좋지 않으며 코드를 유지 관리하기 위해 가능한 경우 피해야합니다. "모든 가능한가에있는 경우"이 있다는 것을 인정합니다 말했다 일부 스위치가 존재해야합니다 경우는, 그 경우에, 하나의 장소에 그것을 아래로 유지하려고, 적은 기계에 의존을 유지하게 하나 (이 용이합니다 그래서 실수로 코드를 제대로 분리하지 않으면 동기화에 필요한 5 곳 중 1 곳을 놓치게됩니다.
Shadow Man

10

중요한 분기없이 전략 패턴을 구현할 수 있습니까?

, 모든 전략 구현이 등록되는 해시 맵 / 사전을 사용합니다. 팩토리 방식은

Class strategyType = allStrategies[orderType];
return runtime.create(strategyType);

모든 전략 구현은 팩토리를 orderType 및 클래스 작성 방법에 대한 팩토리로 등록해야합니다.

factory.register(NEW_ORDER, NewOrder.class);

언어에서 지원하는 경우 정적 생성자를 사용하여 등록 할 수 있습니다.

register 메소드는 해시 맵에 새로운 값을 추가하는 것 이상을 수행하지 않습니다.

void register(OrderType orderType, Class class)
{
   allStrategies[orderType] = class;
}

[업데이트 2012-05-04]
이 솔루션은 대부분의 시간을 선호하는 원래 "스위치 솔루션"보다 훨씬 복잡합니다.

그러나 전략이 자주 변하는 환경 (예 : 고객, 시간 등에 따라 가격 계산)이 IoC 컨테이너와 결합 된이 해시 맵 솔루션은 좋은 솔루션이 될 수 있습니다.


3
이제 스위치 / 케이스를 피하기 위해 전략의 전체 해시 맵이 있습니까? 행의 행 factory.register(NEW_ORDER, NewOrder.class);보다 행이 더 깨끗하거나 OCP 위반이 적 case NEW_ORDER: return new NewOrder();습니까?
pdr

5
전혀 깨끗하지 않습니다. 반 직관적입니다. 공개 폐쇄 원칙을 개선하기 위해 간결하고 바보 같은 원칙을 희생합니다. 제어 역전 및 의존성 주입에도 동일하게 적용됩니다. 간단한 솔루션보다 이해하기가 더 어렵습니다. 문제는 "중요한 분기가없는 구현 ..."이 아니라 "보다 직관적 인 해결책을 만드는 방법"이 아닙니다.
k3b

1
분기를 피한 것을 보지 못했습니다. 구문을 방금 변경했습니다.
pdr

1
필요할 때까지 클래스를로드하지 않는 언어로이 솔루션에 문제가 있습니까? 정적 코드는 클래스가로드 될 때까지 실행되지 않으며 다른 클래스가이를 참조하지 않으므로 클래스가로드되지 않습니다.
케빈 클라인

2
개인적으로 저는이 방법이 마음에 듭니다. 전략을 변경 불가능한 코드로 취급하지 않고 변경 가능 데이터 로 취급 할 수 있기 때문 입니다.
Tacroy

2

"전략"은 적어도 한 번은 대체 알고리즘 중에서 선택해야한다는 것 입니다. 프로그램 어딘가에서 누군가 또는 사용자 나 프로그램을 결정해야합니다. IoC, 리플렉션, 데이터 파일 평가 기 또는 스위치 / 케이스 구성을 사용하여이를 구현해도 상황은 바뀌지 않습니다.


한 번 말씀하신 내용에 동의하지 않습니다 . 전략은 런타임에 교환 할 수 있습니다. 예를 들어 표준 ProcessingStrategy를 사용하여 요청을 처리하지 못한 후 VerboseProcessingStrategy를 선택하고 처리를 다시 실행할 수 있습니다.
dmux

@dmux : 물론, 그에 따라 대답을 편집했습니다.
Doc Brown

1

전략 패턴은 가능한 동작을 지정할 때 사용되며 시작시 핸들러를 지정할 때 가장 적합합니다. 사용할 전략의 인스턴스 지정은 중재자, 표준 IoC 컨테이너, 설명하는 팩토리 또는 컨텍스트에 따라 올바른 팩토리를 사용하여 수행 할 수 있습니다 (전략은 더 광범위한 사용의 일부로 제공되기 때문에) 그것을 포함하는 클래스의).

데이터를 기반으로 다른 메소드를 호출해야하는 이런 종류의 동작을 위해, 현재 사용중인 언어가 제공하는 다형성 구문을 사용하는 것이 좋습니다. 전략 패턴이 아닙니다.


1

내가 일하는 다른 개발자와 이야기하면서 Java와 관련된 또 다른 흥미로운 솔루션을 찾았지만 아이디어가 다른 언어로 작동한다고 확신합니다. 클래스 참조를 사용하여 열거 형 (또는 맵, 너무 중요하지 않음)을 구성하고 리플렉션을 사용하여 인스턴스화하십시오.

enum FactoryType {
   Type1(Type1.class),
   Type2(Type2.class);

   private Class<? extends Type> clazz;

   private FactoryType(Class<? extends Type> clazz) {
      this.clazz = clazz;
   }

   public Class<? extends Type> getTypeClass() {
      return clazz;
   }
}

이렇게하면 팩토리 코드가 크게 줄어 듭니다.

public Type create(FactoryType type) throws Exception {
   return type.getTypeClass().newInstance();
}

(잘못된 오류 처리는 무시하십시오-예제 코드 :)

빌드가 여전히 필요 하므로 가장 유연하지는 않지만 코드 변경을 한 줄로 줄입니다. 나는 데이터가 팩토리 코드와 분리되어 있다는 것을 좋아한다. 추상 클래스 / 인터페이스를 사용하여 일반적인 메소드를 제공하거나 특정 생성자 서명을 강제하기 위해 컴파일 타임 주석을 작성하여 매개 변수 설정과 같은 작업을 수행 할 수도 있습니다.


이것이 홈 페이지로 되돌아 갔는지 확실하지 않지만 이것은 좋은 대답이며 Java 8에서는 이제 반영하지 않고 생성자 참조를 만들 수 있습니다.
JimmyJames

1

각 클래스에 고유 한 주문 유형을 정의하여 확장 성을 향상시킬 수 있습니다. 그런 다음 공장에서 일치하는 것을 선택합니다.

예를 들면 다음과 같습니다.

public interface IOrderStrategy
{
    OrderType OrderType { get; }
}

public class NewOrder : IOrderStrategy
{
    public OrderType OrderType { get; } = OrderType.NewOrder;
}

public class OrderFactory
{
    private IEnumerable<IOrderStrategy> _strategies;

    public OrderFactory(IEnumerable<IOrderStrategy> strategies) // Injected by IoC container
    {
        _strategies = strategies;
    }

    public IOrderStrategy Create(OrderType orderType)
    {
        IOrderStrategy strategy = _strategies.FirstOrDefault(s => s.OrderType == orderType);

        if (strategy == null)
            throw new ArgumentException("Invalid order type.", nameof(orderType));

        return strategy;
    }
}

1

수용 가능한 지점 수에 제한이 있어야한다고 생각합니다.

예를 들어, switch 문 안에 8 개 이상의 사례가있는 경우 코드를 다시 평가하고 리팩터링 할 수있는 항목을 찾습니다. 종종 특정 사례를 별도의 공장으로 그룹화 할 수 있다는 것을 알았습니다. 여기에는 전략을 세우는 공장이 있다고 가정합니다.

어느 쪽이든, 당신은 이것을 피할 수 없으며 어딘가에서 작업중 인 객체의 상태 또는 유형을 확인해야합니다. 그런 다음이를위한 전략을 세우십시오. 우려의 좋은 오래된 분리.

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