기존 코드
(회사 코드중 일부를 추출해서 간단한 예시로 변형한 코드입니다.)
기존 코드는 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에도 부합하는 디자인 패턴입니다.