팩토리 패턴 (Factory Pattern)

조성권·2022년 9월 3일
1
post-thumbnail

오늘은 팩토리 패턴에 대해 알아보고 예제를 살펴보도록 하겠습니다.

팩토리 패턴이란?

팩토리 패턴은 디자인 패턴 중, 실무에서도 자주 사용되는 주요한 패턴들 중 하나라고 볼 수 있다.
그렇다면 팩토리 패턴의 이론적인 정의는 무엇인지 한번 살펴보도록 하자.

  • 팩토리 패턴
    팩토리 패턴은 생성 패턴 중 하나로 인스턴스를 만드는 절차를 추상화하는 패턴이라고 할 수 있다.
    인스턴스를 생성하기 위한 인터페이스는 정의하되 어떤 클래스의 인스턴스를 만들지는 서브 클래스에서 결정하도록 함으로써 결합도를 낮추는 효과가 있다.

팩토리 패턴은 왜 필요한가?

객체지향(=OOP)의 특징 중, 하나는 바로 추상화이다.
객체의 속성 중 공통되면서 중요한 성질들만을 추출해서 개략화하고 실제 구현체는 각각의 서브 클래스에서 구현하도록 맡김으로써 결합도를 낮추고 코드의 재사용성을 증가시키는 것을 목표로 한다.

같은 맥락에서 JAVA는 설계 단계에서 상속(=extends)보다 복합(=implements)를 사용하는 방향으로 가닥을 잡는 상황이며 팩토리 패턴은 이러한 상황에서 큰 장점을 지니고 자주 사용된다.

팩토리 패턴의 구조?

그렇다면 팩토리 패턴의 구조는 어떻게 생겨먹었는지 간단히 보도록 하자.

간단히 구조를 그려보자면 위와 같다고 볼 수 있다.

  1. Controller로 요청 인입
  2. Abstract Service(=추상 서비스)에 함수 호출
  3. Factory를 통해 어떤 Service 객체에서 처리해야할지 판단 및 호출
  4. 해당 Service(ServiceA / ServiceB / ServiceC)는 해당 작업 수행

실제 구현하면 어떻게?

위에서 보여준 구조를 실제 구현해보도록 하자.

아래 예시 코드에 대한 흐름을 간단히 설명한다면 다음과 같다.
Controller는 GET형태로 param을 받게 되며 이 param은 어떤 Service를 호출해야하는지 serviceType에 대한 Input이라고 보면 된다.
정상적으로 호출되면 param으로 들어온 ServiceType에 해당하는 함수가 호출되어 화면에 출력되는 것을 볼 수 있을 것이다.


  • Controller
/**
	MainController.java
*/
@RestController
@RequestMapping("/main")
public class MainController {

    @Autowired
    ExecuteService executeService;

    @GetMapping("/print/{param}")
    public String printParam(@PathVariable("param") String param) {
        String result = "FAIL";

        System.out.println("param: " + param);
        ServiceType serviceType = ServiceType.findByServiceCode(param);
        result = executeService.printService(serviceType, "가 실행되었습니다.");

        return result;
    }
}

  • ServiceType (enum으로 관리)
/**
	ServiceType.java
*/
@Getter
public enum ServiceType {

    SERVICE_A("A","ServiceA"),
    SERVICE_B("B","ServiceB"),
    SERVICE_C("C","ServiceC")
    ;

    private String serviceCode;
    private String serviceName;

    ServiceType(String serviceCode, String serviceName){
        this.serviceCode = serviceCode;
        this.serviceName = serviceName;
    }

    public static ServiceType findByServiceCode(final String serviceCode){
        return Arrays.stream(ServiceType.values())
                .filter(it -> it.serviceCode.equals(serviceCode))
                .findAny()
                .orElseThrow(() -> new RuntimeException("해당 하는 서비스가 존재하지 않습니다."));
    }
}

1차적으로 여기까지 본다면 우리는 Controller에서 param이라는 값을 받는다.
그 후, 우리는 이 param이라는 값을 가지고 ServiceType으로 넘어가 해당 ServiceCode에 매핑되는 ServiceType객체를 Return 받는다.


  • ExecuteService (=Abstract Service)
/**
	ExecuteService.java
*/
@Service
public class ExecuteService {

    @Autowired
    ServiceFactory serviceFactory;

    public String printService(ServiceType serviceType, String str) {
        return serviceFactory.getService(serviceType).printServiceName(str);
    }
}

이제 우리는 Abstract Service(=추상 서비스)단으로 들어왔다.
앞서 enum을 통해 찾아온 ServiceType객체와 추가적으로 출력하고자 하는 문자열(=str)을 가지고 printService함수를 호출한다.


  • ServiceFactory (=Factory)
/**
	ServiceFactory.java
*/
@Component
public class ServiceFactory {

    public final Map<String, CommonService> serviceTypeMap = new HashMap<>();

    public ServiceFactory(List<CommonService> commonServiceList){
        for(CommonService commonService : commonServiceList){
            serviceTypeMap.put(commonService.getServiceName(), commonService);
        }
    }

    public CommonService getService(ServiceType serviceType){
        return serviceTypeMap.get(serviceType.getServiceName());
    }
}

이제 오늘 주제이기도 한 Factory가 나설 차례이다.
위 코드에 대해 한줄씩 차례대로 살펴본다면 우리는 먼저 Map객체생성자를 봐야한다.
어플리케이션이 시작됨과 동시에 컴포넌트(@Component)로 등록된 ServiceFactory의 생성자가 호출된다.
이 때, 뒤이어 나올 CommonService을 implements하고 있는 모든 실제 구현체가 포함된 Service그들의 Service명과 함께 Map객체에 key/value형태로 등록되게 된다.

이후, Abstract Service단에서 getService 함수가 호출되면 앞서 등록된 Map객체에서 이에 해당하는 Service를 불러주는 것이다.


/**
	CommonService.java
*/
public interface CommonService {
    public String getServiceName();
    public String printServiceName(String str);
}
/**
	ServiceA.java
*/
@Service
public class ServiceA implements CommonService{

    @Override
    public String getServiceName(){
        return "ServiceA";
    }

    @Override
    public String printServiceName(String str){
        return "[ServiceA] printServiceName" + str;
    }
}
/**
	ServiceB.java
*/
@Service
public class ServiceB implements CommonService {
    @Override
    public String getServiceName() {
        return "ServiceB";
    }

    @Override
    public String printServiceName(String str) {
        return "[ServiceB] printServiceName" + str;
    }
}
/**
	ServiceC.java
*/
@Service
public class ServiceC implements CommonService {
    @Override
    public String getServiceName() {
        return "ServiceC";
    }

    @Override
    public String printServiceName(String str) {
        return "[ServiceC] printServiceName" + str;
    }
}

이제 마지막으로 실제 구현체가 포함된 Service단이다.
CommonService에는 실 구현체들이 공통으로 사용하는 함수명에 대해 기재되어 있다.
이를 implements하는 Service들은 모두 해당 공통함수를 Override하기 때문에 앞선 Factory에서 이들을 호출할 수 있는 것이다.

그렇다면 마지막으로 실제 output을 확인해보도록 하자.

  1. Request URL: ~/main/print/A
  1. Request URL: ~/main/print/B
  1. Request URL: ~/main/print/C
profile
천천히, 완벽히 배워나가고자 하는 웹 서비스 엔지니어

0개의 댓글