오늘은 팩토리 패턴에 대해 알아보고 예제를 살펴보도록 하겠습니다.
팩토리 패턴은 디자인 패턴 중, 실무에서도 자주 사용되는 주요한 패턴들 중 하나라고 볼 수 있다.
그렇다면 팩토리 패턴의 이론적인 정의는 무엇인지 한번 살펴보도록 하자.
- 팩토리 패턴
팩토리 패턴은 생성 패턴 중 하나로 인스턴스를 만드는 절차를 추상화하는 패턴이라고 할 수 있다.
인스턴스를 생성하기 위한 인터페이스는 정의하되 어떤 클래스의 인스턴스를 만들지는 서브 클래스에서 결정하도록 함으로써 결합도를 낮추는 효과가 있다.
객체지향(=OOP)의 특징 중, 하나는 바로 추상화이다.
객체의 속성 중 공통되면서 중요한 성질들만을 추출해서 개략화하고 실제 구현체는 각각의 서브 클래스에서 구현하도록 맡김으로써 결합도를 낮추고 코드의 재사용성을 증가시키는 것을 목표로 한다.
같은 맥락에서 JAVA는 설계 단계에서 상속(=extends)보다 복합(=implements)를 사용하는 방향으로 가닥을 잡는 상황이며 팩토리 패턴은 이러한 상황에서 큰 장점을 지니고 자주 사용된다.
그렇다면 팩토리 패턴의 구조는 어떻게 생겨먹었는지 간단히 보도록 하자.
간단히 구조를 그려보자면 위와 같다고 볼 수 있다.
위에서 보여준 구조를 실제 구현해보도록 하자.
아래 예시 코드에 대한 흐름을 간단히 설명한다면 다음과 같다.
Controller는 GET형태로 param을 받게 되며 이 param은 어떤 Service를 호출해야하는지 serviceType에 대한 Input이라고 보면 된다.
정상적으로 호출되면 param으로 들어온 ServiceType에 해당하는 함수가 호출되어 화면에 출력되는 것을 볼 수 있을 것이다.
/**
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.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.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.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을 확인해보도록 하자.