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


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.