본문 바로가기

디자인패턴

레거시 코드 전략패턴 적용기

기존 코드

(회사 코드중 일부를 추출해서 간단한 예시로 변형한 코드입니다.)

기존 코드는 id를 조회하기 위해 여러 Repository에 의존적이고 if-else 문이 반복되고 있습니다.

이는 코드의 가독성을 저하시키며, 새로운 type 혹은 value가 추가될 경우 다시 else문이 추가되어야 한다는 단점이 존재합니다.

    public long getCustomerId(String type, String value) {
        long id = 0;

        if (type.equals("TypeA")) {

            id = aRepository.readId(value);
        } else if (type.equals("TypeB")) {

            id = bRepository.readId(value);
        } else if (uniqueValueType.equals("TypeC")) {

            id = cRepository.readId(value);
        } else if (uniqueValueType.equals("TypeD")) {

            id = dRepository.readId(value);
            // value로 AEntity 조회 후 없을 경우 BEntity 조회
                }

            Optional<AEntity> aEntity = ERepository.readByValue(value);

            if (aEntity.isPresent()) {
                id = aEntity.get().getId();
            } else {
                id = dRepository.readByValue(value).get().getEntity().getId()
            }
        }
        return customerId;
    }
}

이러한 코드를 개선하기 위해 전략패턴을 학습하였고 코드에 적용해보았습니다.

전략패턴 적용

우선 공통적으로 사용될 전략 인터페이스를 생성해줍니다.

public interface MyStrategy {

    long readByValue(String value);

    String getStrategyName();
}

이제 각 type 혹은 value에 의해 결정되었던 값들을 하나의 전략으로 묶어줍니다.

@Component
@RequiredArgsConstructor
public class ATypeStrategy implements MyStrategy {

    private final ARepository aRepository;

    @Override
    public long readByValue(String value) {

        return aRepository.readId(value);
    }

    @Override
    public String getStrategyName() {
        return "TypeA";
    }
}
@Component
@RequiredArgsConstructor
public class BTypeStrategy implements MyStrategy {

    private final BRepository aRepository;

    @Override
    public long readByValue(String value) {

        return bRepository.readId(value);
    }

    @Override
    public String getStrategyName() {
        return "TypeB";
    }
}

각 타입에 맞는 전략을 구현하였다면, 인터페이스가 적절한 전략을 찾을 수 있도록 StrategyContext를 만들어줍니다.

@Component
public class MyStrategyContext {

    private final Map<String, MyStrategy> strategyMap = new HashMap<>();

    @Autowired
    public MyStrategyContext(List<MyStrategy> strategies) {
                // strategyMap 에 전략 이름과 전략을 넣어줍니다.
        strategies.forEach(strategy -> strategyMap.put(strategy.getStrategyName(), strategy));
    }

    public long getCustomerId(String type, String value) {

        CustomerQueryStrategy strategy = strategyMap.get(uniqueValueTYpe);
        return strategy.readByValue(uniqueValue);
    }
}

위와 같이 전략을 Map에 삽입한 후, 입력받는 Type과 Value를 통해 적절한 전략이 선택되어 반환되게 됩니다.

이제 id를 찾기 위해 다음 코드만 입력하면 됩니다.

long id = myStrategyContext.readByValue(uniqueValueType, uniqueValue);

수많은 if-else문과 비교해보면 엄청난 개선이라고 볼 수 있습니다.

또다른 장점

전략패턴을 적용했을 때 더 크게 다가오는 장점은 전략의 추가, 삭제에도 id를 구하는 코드는 전혀 영향을 받지 않는다는 것입니다.

만약 TypeE가 추가되고, TypeE는 ERepository에 의존한다면 어떻게 할 수 있을까요?

@Component
@RequiredArgsConstructor
public class ETypeStrategy implements MyStrategy {

    private final ERepository aRepository;

    @Override
    public long readByValue(String value) {

        return eRepository.readId(value);
    }

    @Override
    public String getStrategyName() {
        return "TypeE";
    }
}

새로운 ETypeStrategy 클래스만 생성해주면 기존 코드 수정 없이 서비스를 운영할 수 있고, OCP에도 부합하는 디자인 패턴입니다.

'디자인패턴' 카테고리의 다른 글

데코레이터 패턴  (0) 2024.05.07