인턴을 진행하면서 실제 실무에서 사용하는 코드를 접할 수 있는 기회를 가지게 됐다. 객체 지향의 장점을 살리기 위한 다양한 디자인 패턴들이 코드에 자연스럽게 녹아있는걸 알 수 있었다. 최근 클린 아키텍처에 대한 관심이 커져서 패키지 구조나 계층 간 매핑 전략 등에 신경을 많이 쓰고 있었지만, 이번을 계기로 객체 지향의 특징을 잘 지키는 코드를 작성하는 방법에 대한 고민을 많이 하게 되었다.
그러던 중, 망나니 개발자님의 블로그 글 중에 팩토리 패턴에 대한 글을 발견했고, 앞으로 팀 프로젝트나 실무에서 많이 사용할만한 패턴인 것 같아서 블로그에 정리를 하려고 한다.
// 구체 클래스들이 구현하게 될 LoginService 인터페이스이다.
public interface LoginService {
// if-else문을 통해서 분기처리를 하는 대신, 지원 여부를 구체 클래스 내에서 진행한다.
boolean supports(LoginType loginType);
void login();
}
public class WebLogin implements LoginService{
// 다양한 로그인 방식이 추가될때마다 LoginService를 구현해서 만들기만 하면 된다.
// 외부적으로 추가되는 코드는 하나도 없다.
@Override
public boolean supports(LoginType loginType) {
return loginType == LoginType.WEB;
}
@Override
public void login() {
System.out.println("web login!");
}
}
@RequiredArgsConstructor
public class LoginFactory {
private final List<LoginService> loginServiceList;
// 이미 생성된 객체를 재활용할 수 있게 해주는 캐시
private final Map<LoginType,LoginService> factoryCache;
/**
* 팩토리에서 if-else를 사용해서 특정 타입을 선택하는게 아니라, 타입 판별을 구체 클래스 자체에서 처리
*/
public LoginService find(final LoginType loginType)
{
// 먼저 캐시에 해당 Service가 있는지 체크
LoginService loginService = factoryCache.get(loginType);
// 만약 있다면 추가적인 과정 없이 바로 반환
if(loginService != null)
{
return loginService;
}
// 전체 리스트를 순회하면서 타입에 맞는 객체를 찾음
loginService = loginServiceList.stream()
.filter(v -> v.supports(loginType))
.findFirst()
.orElseThrow();
// 찾은 객체를 캐시에 넣음
factoryCache.put(loginType,loginService);
return loginService;
}
}