DesignPattern 적용하기 #2 팩토리 메서드 패턴

강철진·2023년 3월 16일
0

DesignPattern

목록 보기
3/5
post-thumbnail

배경

그루비 솔루션의 가장 대표적인 기능은 상품 추천입니다. 고객사가 원하는 추천 알고리즘을 선택하여 캠페인을 생성하면 사용자의 현재 행동 패턴에 따라 그에 알맞은 상품을 추천해주는 기능입니다. 이러한 알고리즘은 각자의 로직이 존재하고 이를 각자의 클래스로 관리한다면 개발자는 해당 알고리즘의 클래스만 확인하면 될 것입니다.

기존 코드의 문제점

@Service
public class AlgorithmProcess {
	...
    public List<String> getRecommend() {
    	...
        switch (processType) {
            case PR01:
            	//로직
            case PR02:
            	//로직
            case PR03:
            	//로직
            case PR04:
            	//로직
            case PR05:
            	//로직
            ...
        }
    }
    ...
	
}

다음과 같은 형태를 가지고 있습니다. 모든 알고리즘을 저렇게 관리하고 있다 보니 보기에도 불편하지 않나요? 문제는 저 이후에도 추천 상품이 가져오지 못하거나 부족할 때 대체 알고리즘 로직을 타게 되는데 대체 알고리즘 로직에서도 저러한 형태를 가지고 있습니다.

저는 하나의 클래스에서 너무 많은 로직을 처리하고 있다는 것이 분명해보였고, 이를 해결하기 위해 팩토리 메서드 패턴을 활용하기로 했습니다.

팩토리 메서드 패턴이란?

팩토리 메서드는 부모 클래스에서 객체들을 생성할 수 있는 인터페이스를 제공하지만, 자식 클래스들이 생성될 객체들의 유형을 변경할 수 있도록 하는 생성 패턴입니다.

Class Diagram

전체적인 개발 흐름은 다음과 같습니다.

  1. 기본적으로 사용할 추천 프로세스를 인터페이스로 생성합니다. (BaseProcess)
  2. 각자의 프로세스는 클래스로 나누어 해당 인터페이스들을 상속받습니다.
  3. 팩토리 클래스에서는 해당 인터페이스를 상속받은 빈을 맵으로 관리합니다.
  4. 실제 서비스에서는 팩토리 클래스를 주입받아 프로세스 타입에 따라 빈을 얻어 실제 비즈니스 로직 메서드를 실행합니다.

팩토리 메서드 패턴 적용 코드

BaseProcess.java

public interface BaseProcess {
    String getProcessResult();
    ProcessType getProcessType();
}

Process1.java

@Component
public class Process1 implements BaseProcess {
    @Override
    public String getProcessResult() {
        return "프로세스 결과 1";
    }

    @Override
    public ProcessType getProcessType() {
        return ProcessType.PR01;
    }
}

BaseProcessFactory.java

@Component
public class BaseProcessFactory {
    private final Map<ProcessType, BaseProcess> baseProcessMap = new HashMap<>();

    public BaseProcessFactory(List<BaseProcess> baseProcessList) {
        for(BaseProcess baseProcess : baseProcessList) {
            this.baseProcessMap.put(baseProcess.getProcessType(), baseProcess);
        }
    }

    public BaseProcess getBaseProcess(ProcessType processType) {
        return baseProcessMap.get(processType);
    }
}

ServiceImpl.java

@Service
public class ServiceImpl implements Service {
	private final BaseProcessFactory baseProcessFactory; // 빈주입
	...
    public String getProcessResult(ProcessType processType) {
    	// 팩토리에서 해당 타입의 프로세스 빈의 getProcessResult 실행
    	return baseProcessFactory.getBaseProcess(processType).getProcessResult
    }
    ...
	
}

여기서 핵심은 세번째 BaseProcessFactory 클래스입니다. 각각의 프로세스 구현체들은 자신의 프로세스 타입이 무엇인지 리턴해주는 getProcessType 메서드를 가지고 있기때문에 리스트 형태로 받아온 빈들을 Map 에 담아 관리할 수 있게 됩니다.

ServiceImpl 에서 처럼 해당 프로세스 코드만 넘겨주면 해당 프로세스의 빈을 얻어올 수 있게됩니다. 팩토리 메서드 패턴을 사용하고나니 switch문 지옥에서 빠져나온 코드를 확인하실 수 있습니다.

이후 추가적인 프로세스 로직이 생기면 해당 인터페이스를 상속받아 구현만 하면 되고, 수정이 발생 되었을 때도 해당 프로세스 클래스만 수정하면 되기 때문에 유지 보수에도 유리합니다.

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

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

0개의 댓글