DesignPattern 적용하기 #3 전략패턴

강철진·2023년 3월 17일
0

DesignPattern

목록 보기
4/5
post-thumbnail

배경

고객사는 자신들이 원하는 조건으로 고객을 타겟팅하기 위해 그루비 솔루션을 사용하여 세그먼트를 설정합니다. 그리고 그러한 조건은 사용 브라우저, 현재 상품의 이름, 현재 페이지 URL 등 다양한 조건들이 있습니다. 다양한 세그먼트가 존재하지만 결국 알고자 하는 내용은 해당 고객이 해당 세그먼트 조건을 만족하느냐 입니다.

기존 코드의 문제점

	private Object check(parameter1, parameter2) {
    	Class cls = ...
        Mehtod method = ...
        return method.invoke();
        ...
        return 0;
    }

보시면 아시겠지만 자바의 리플랙션을 활용하여 구현되어있습니다. 간단히 리플랙션을 설명하자면 구체적인 클래스 타입을 알지 못하더라도 그 클래스의 메서드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API 입니다. 리플랙션에 대해서는 설명하자면 너무 길어지기 때문에 추후 시간이 되면 정리해서 블로그에 올리도록 하겠습니다.

어쨋든 제가 아는 리플랙션은 어떠한 클래스를 사용할지 모를 때 사용해야 의도에 맞게 사용하는 것입니다. 그러나 현재 세그먼트는 각자 고유의 코드 값을 DB에 적재하고 있기때문에 어떤 클래스의 어떤 메서드를 호출해야할지 알 수 있습니다.

그러나 리플랙션을 이용해 패키지 이름과 클래스 이름, 메서드 이름을 참조하여 객체 생성 및 해당 메서드를 호출합니다. 심지어 className 과 methodName 은 DB 를 참조하여 실행합니다.

이렇게 되면 패키지가 변경되거나, 혹은 클래스 이름, 메서드 이름을 마음대로 변경할 수 없습니다. 해당 로직은 DB 의존적인 코드가 된다는 얘기입니다. 또한 어떤 메서드가 어디서 사용되는지 추적이 어렵습니다. 이러한 문제를 해결하기 위해 전략패턴을 사용하여 리플랙션 코드를 걷어내고자 했습니다.

전략패턴이란?

전략 패턴은 알고리즘들의 패밀리를 정의하고, 각 패밀리를 별도의 클래스에 넣은 후 그들의 객체들을 상호교환할 수 있도록 하는 행동 디자인 패턴입니다.

Class Diagram

전체적인 코드 작성 흐름은 다음과 같습니다.

  1. 세그먼트 조건은 크게 두가지 타입으로 나뉩니다. (A 와 B라고 칭하겠습니다.) 따라서 해당 인터페이스를 하나씩 선언하고 리턴 타입이 다른 execute(룰체크 로직) 함수를 가집니다.
  2. 이 두가지 인터페이스를 상속받은 AllTypeStrategy 인터페이스가 있습니다.
  3. 이러한 인터페이스의 빈을 관리하는 팩토리 클래스가 있습니다.(DesignPattern 적용하기 #2 팩토리 메서드 패턴)
  4. 각 세그먼트 클래스(Segment1, Segment2, Segment3) 내부에는 ATypeStrategy, BTypeStrategy 상속받은 inner class 가 존재합니다.
    inner class 를 사용한 이유는 너무 많은 클래스를 생성해 관리포인트가 늘어나지 않게 하기 위함입니다. 또한 해당 클래스의 책임이 너무 많아져 클래스 파일로 분리시켜야할 때가 왔을 때를 대비할 수 있습니다.

CommonStrategy.java

public interface CommonStrategy {
    CheckType getCheckType();
}

ATypeStrategy.java

public interface ATypeStrategy extends CommonStrategy {
    boolean execute();
    CheckType getCheckType();
}

BTypeStrategy.java

public interface BTypeStrategy extends CommonStrategy {
    boolean execute();
    CheckType getCheckType();
}

TypeFactory.java

@Component
public class TypeFactory<T extends CommonStrategy> {
    private final Map<CheckType, ATypeStrategy> aTypeStrategyHashMap = new HashMap<>();
    private final Map<CheckType, BTypeStrategy> bTypeStrategyHashMap = new HashMap<>();

    public TypeFactory(List<T> typeStrategies) {
        for (T typeStrategy : typeStrategies) {
            if(typeStrategy instanceof ATypeStrategy)
                this.aTypeStrategyHashMap.put(typeStrategy.getCheckType(), (ATypeStrategy) typeStrategy);
            else if(typeStrategy instanceof BTypeStrategy)
                this.bTypeStrategyHashMap.put(typeStrategy.getCheckType(), (BTypeStrategy) typeStrategy);
        }
    }

    public ATypeStrategy getATypeStrategy(CheckType checkType) {
        return aTypeStrategyHashMap.get(checkType);
    }

    public BTypeStrategy getBTypeStrategy(CheckType checkType) {
        return bTypeStrategyHashMap.get(checkType);
    }
}

Segment1.java

public class Segment1 {
    protected static final CheckType TYPE = CheckType.SEGMENT1;
    
    @Component
    public static class Segment1A implements ATypeStrategy {

        @Override
        public boolean execute() {
            return false;
        }

        @Override
        public CheckType getCheckType() {
            return TYPE;
        }
    }

    @Component
    public static class SegmentB implements BTypeStrategy {

        @Override
        public int execute() {
            return 0;
        }

        @Override
        public CheckType getCheckType() {
            return TYPE;
        }
    }
}

SegmentCheckProcessImpl.java

@Service
public class SegmentCheckProcessImpl implements SegmentCheck {
	private final TypeFactory<CommonStrategy> typeFactory; // 빈주입
	...
    
    public boolean isATypeSegmentPass(String segmentType) {
        CheckType type = CheckType.getCheckType(segmentType);
        // 팩토리 클래스에서 A인터페이스를 상속받은 해당 체크타입 빈을 받아 로직 수행
        boolean result = typeFactory.getATypeStrategy(type).execute();
		return result;
    }
    
    public boolean isBTypeSegmentPass(String segmentType) {
    	CheckType type = CheckType.getCheckType(segmentType);
        // 팩토리 클래스에서 B인터페이스를 상속받은 해당 체크타입 빈을 받아 로직 수행
        boolean result = typeFactory.getATypeStrategy(type).execute();
		return result;
    }
    ...
}

기존 리플랙션 코드를 걷어내고 나니 DB 의존성도 제거되고 코드가 훨씬 간결해졌습니다. 내부 로직은 다르지만 하는 일은 같으므로 전략패턴과 팩토리 메서드 패턴을 사용함으로 이제 하나의 세그먼트에 대해 내부로직을 확인하거나 수정이 있을 때 해당 클래스 파일만 보면 되므로 유지보수에도 유리합니다.

썸네일 이미지 및 학습자료 출처
https://refactoring.guru/ko/design-patterns/strategy

profile
자바/스프링 백엔드 개발자입니다.

0개의 댓글