추상 팩토리
이번 주제는 프로세스 로직을 추상 팩토리 패턴을 적용하여 Spring에서 어떻게 사용할 수 있을지에 대해서 작성하려고 합니다.
추상 팩토리 패턴이란?
개인적인 견해로는 추상 팩토리 패턴은 팩토리 메서드 패턴보다 결합도를 낮추고 응집도를 올려주는 패턴이라고 생각합니다. 왜 결합도를 낮추고 응집도를 높히는지 알려면 먼저 팩토리 메서드 패턴 구조를 알아야 하는데 이 부분은 이전 글인 https://devssul.tistory.com/26를 참고하시면 되겠습니다.
팩토리 메서드는 프로세스 구현 객체를 반환 받기 위해 추상 클래스(혹은 부모 클래스)와 상속 받는 구현체 구조를 가지게 되고 반환 받은 프로세스 구현 객체를 추상 클래스(혹은 부모 클래스)에서 제어하기 때문에 추상 클래스(혹은 부모 클래스)에서 제어 부분을 변경하게 되면 상속 받은 모든 자식 클래스에 영향이 전파 되는 결합이 생깁니다.
추상 팩토리 패턴은 프로세스 구현 객체 반환 부분이 메서드 오버라이드 구조가 아닌 완전한 객체 생성 구조를 가지고 있기 때문에 변경 시 발생 되는 영향에 전파가 없으며 클래스로 프로세스 처리 구현 객체들을 생성 관리하기 때문에 응집도를 높힐 수 있습니다.
더 자세한 내용은 추상 팩토리 패턴 (refactoring.guru)에서 확인 하시면 되겠습니다.
예제
예제는 https://devssul.tistory.com/26을 기준으로 하겠습니다. 이전 글에서는 회원가입만 있고 검증 하는 부분이 빠져있어 회원가입 & 검증을 추상 팩토리 패턴을 적용하도록 하겠습니다.
큰 흐름의 클래스 다이어그램을 살펴보면 MemberJoinAbsFactory
인터페이스에서 회원가입 프로세스 구현체를 반환하는 createMemberJoin
메서드와 검증 프로세스 구현체를 반환하는 createMemberJoinValidate
메서드가 선언 되어 있고 카카오톡 회원가입 프로세스 구현체들을 반환하는 KakaoMemberJoinAbsFactory
구현체와 기본 회원가입 프로세스 구현체들을 반환하는 DefaultMemberJoinAbsFactory
구현체가 존재합니다.
카카오톡 회원가입 추상 팩토리 패턴
카카오톡 회원가입 프로세스 추상 팩토리 패턴 부분만 상세 클래스 다이어그램으로 보면 아래와 같습니다.
회원가입 프로세스 구현체
/**
* <pre>
* 카카오톡 회원가입 처리 프로세스 클래스
* </pre>
*/
public class KakaoMemberJoinAbsFactoryProcessImpl implements MemberJoinAbsFactoryProcess {
private final KakaoMemberJoinEntity kakaoMemberJoinEntity;
public KakaoMemberJoinAbsFactoryProcessImpl(KakaoMemberJoinEntity kakaoMemberJoinEntity) {
this.kakaoMemberJoinEntity = kakaoMemberJoinEntity;
}
/**
* {@link MemberJoinAbsFactoryProcess#join()}
* 카카오톡 회원가입인 경우엔 응답 받은 카카오톡 전문에서 특정 데이터만 추출하여
* 기본 회원가입 처리도 동시 처리 진행한다.
*/
@Override
public void join() {
KakaoMemberJoinWriteImpl kakaoMemberJoinWriteImpl = MemberJoinFactory.getInstance(KakaoMemberJoinWriteImpl.class);
MemberJoinEntity memberJoinEntity = new MemberJoinEntity(this.kakaoMemberJoinEntity.getKakaoAccountEmail()
, UUID.randomUUID().toString().replace("-", "").substring(0, 10)
, MemberJoinEntity.MemberJoinType.KAKAO);
kakaoMemberJoinWriteImpl.kakaoJoin(this.kakaoMemberJoinEntity);
kakaoMemberJoinWriteImpl.join(memberJoinEntity);
}
}
회원가입 검증 프로세스 구현체
/**
* <pre>
* 카카오톡 회원가입 예외 검증 구현체
* </pre>
*/
public class KakaoMemberJoinValidateAbsFactoryProcessImpl implements MemberJoinValidateAbsFactoryProcess {
private final KakaoMemberJoinEntity kakaoMemberJoinEntity;
public KakaoMemberJoinValidateAbsFactoryProcessImpl(KakaoMemberJoinEntity kakaoMemberJoinEntity) {
this.kakaoMemberJoinEntity = kakaoMemberJoinEntity;
}
/**
* <pre>
* {@link MemberJoinValidateAbsFactoryProcess#validate()}
* </pre>
*/
@Override
public void validate() {
KakaoMemberJoinEntity findKakaoMemberJoinEntity = MemberJoinFactory.getInstance(KakaoMemberJoinReadImpl.class)
.findKakaoMember(this.kakaoMemberJoinEntity.getKakaoAccountEmail());
if(findKakaoMemberJoinEntity.getId() != 0L) {
throw new IllegalStateException("존재하는 회원 입니다.");
}
}
}
추상 팩토리 패턴 구현체
/**
* <pre>
* 카카오톡 회원가입 추상 팩토리 클래스
* </pre>
*/
public class KakaoMemberJoinAbsFactory implements MemberJoinAbsFactory {
/**
* <pre>
* {@link MemberJoinAbsFactory#createMemberJoin(Object)}
* </pre>
*/
@Override
public <T> MemberJoinAbsFactoryProcess createMemberJoin(T joinEntity) {
return new KakaoMemberJoinAbsFactoryProcessImpl((KakaoMemberJoinEntity) joinEntity);
}
/**
* <pre>
* {@link MemberJoinAbsFactory#createMemberJoinValidate(Object)}
* </pre>
*/
@Override
public <T> MemberJoinValidateAbsFactoryProcess createMemberJoinValidate(T validateEntity) {
return new KakaoMemberJoinValidateAbsFactoryProcessImpl((KakaoMemberJoinEntity) validateEntity);
}
}
기본 회원가입 추상 팩토리 패턴
기본 회원가입 추상 팩토리 패턴 클래스 다이어그램은 아래를 참고하시면 되겠습니다.
회원가입 프로세스 구현체
/**
* <pre>
* 기본 회원가입 처리 프로세스 클래스
* </pre>
*/
public class MemberJoinAbsFactoryProcessImpl implements MemberJoinAbsFactoryProcess {
private final MemberJoinEntity memberJoinEntity;
public MemberJoinAbsFactoryProcessImpl(MemberJoinEntity memberJoinEntity) {
this.memberJoinEntity = memberJoinEntity;
}
/**
* {@link MemberJoinAbsFactoryProcess#join()}
*/
@Override
public void join() {
MemberJoinFactory.getInstance(MemberJoinWriteImpl.class)
.join(memberJoinEntity);
}
}
회원가입 검증 프로세스 구현체
/**
* <pre>
* 기본 회원가입 예외 검증 구현체
* </pre>
*/
public class MemberJoinValidateAbsFactoryProcessImpl implements MemberJoinValidateAbsFactoryProcess {
private final MemberJoinEntity memberJoinEntity;
public MemberJoinValidateAbsFactoryProcessImpl(MemberJoinEntity memberJoinEntity) {
this.memberJoinEntity = memberJoinEntity;
}
/**
* <pre>
* {@link MemberJoinValidateAbsFactoryProcess#validate()}
* </pre>
*/
@Override
public void validate() {
MemberJoinEntity findMemberJoinEntity = MemberJoinFactory.getInstance(MemberJoinReadImpl.class)
.findMember(this.memberJoinEntity.getMemberId());
if(!"".equals(findMemberJoinEntity.getMemberId())) {
throw new IllegalStateException("존재하는 회원 입니다.");
}
}
}
추상 팩토리 패턴 구현체
/**
* <pre>
* 기본 회원가입 추상 팩토리 클래스
* </pre>
*/
public class DefaultMemberJoinAbsFactory implements MemberJoinAbsFactory {
/**
* <pre>
* {@link MemberJoinAbsFactory#createMemberJoin(Object)}
* </pre>
*/
@Override
public <T> MemberJoinAbsFactoryProcess createMemberJoin(T joinEntity) {
return new MemberJoinAbsFactoryProcessImpl((MemberJoinEntity)joinEntity);
}
/**
* <pre>
* {@link MemberJoinAbsFactory#createMemberJoinValidate(Object)}
* </pre>
*/
@Override
public <T> MemberJoinValidateAbsFactoryProcess createMemberJoinValidate(T validateEntity) {
return new MemberJoinValidateAbsFactoryProcessImpl((MemberJoinEntity) validateEntity);
}
}
추상 팩토리 패턴을 사용하는 클래스
/**
* <pre>
* 회원가입 트랜잭션 처리 클래스
* </pre>
*/
@Service
public class MemberJoinAbsFactoryServiceImpl {
/**
* <pre>
* 회원가입 프로세스 트랜잭션 처리를 하는 메서드
* </pre>
*
* @param memberJoinAbsFactory 회원가입 추상 팩토리
* @param joinEntity 회원가입 처리에 필요한 엔티티
* @param <T> 회원가입에 유형에 따른 회원정보 엔티티 타입
*/
public <T> void join(MemberJoinAbsFactory memberJoinAbsFactory, T joinEntity) {
MemberJoinAbsFactoryProcess memberJoinAbsFactoryProcess = memberJoinAbsFactory.createMemberJoin(joinEntity);
MemberJoinValidateAbsFactoryProcess memberJoinValidateAbsFactoryProcess = memberJoinAbsFactory.createMemberJoinValidate(joinEntity);
memberJoinValidateAbsFactoryProcess.validate();
memberJoinAbsFactoryProcess.join();
}
}
테스트
카카오톡 회원가입
@Test
void 카카오톡_회원가입() {
KakaoMemberJoinEntity kakaoMemberJoinEntity = new KakaoMemberJoinEntity(13248627872L
, LocalDateTime.now()
, true
, false
, true
, true
, "test@kakao.com"
);
this.memberJoinAbsFactoryServiceImpl.join(new KakaoMemberJoinAbsFactory(), kakaoMemberJoinEntity);
}
결과
c.d.c.r.m.w.t.KakaoMemberJoinWriteImpl : kakaoJoin : [{"id":13248627872,"connectedAt":[2023,1,31,17,19,15,839213600],"kakaoAccountHasEmail":true,"kakaoAccountEmailNeedsAgreement":false,"kakaoAccountIsEmailValid":true,"kakaoAccountIsEmailVerified":true,"kakaoAccountEmail":"test@kakao.com"}]
c.d.c.r.m.w.test.MemberJoinWriteImpl : defaultJoin : [{"memberId":"test@kakao.com","password":"4c2847b227","memberJoinType":"KAKAO"}]
기본 회원가입
@Test
void 일반_회원가입() {
MemberJoinEntity memberJoinEntity = new MemberJoinEntity(UUID.randomUUID().toString().replace("-", "").substring(0, 5)
, UUID.randomUUID().toString().replace("-", "").substring(0, 10)
, MemberJoinEntity.MemberJoinType.DEFAULT);
this.memberJoinAbsFactoryServiceImpl.join(new DefaultMemberJoinAbsFactory(), memberJoinEntity);
}
결과
c.d.c.r.m.w.test.MemberJoinWriteImpl : defaultJoin : [{"memberId":"61c46","password":"545f185990","memberJoinType":"DEFAULT"}]
마치며
보시는바와 같이 프로세스 구현체들을 추상 팩토리 패턴 클래스가 관리하기 때문에 응집도를 높힐 수 있고 순수히 구현체들의 객체만을 반환하기 때문에 전체 프로세스를 어떤 방식으로 구현 하더라도 상관없어 결합를 낮출 수가 있습니다.
그러나 단점으론 상당히 많은 클래스가 생성될 여지가 있기 때문에 꼭 추상 팩토리 패턴이 좋다 추상 메서드 패턴이 좋다 할 순 없습니다. 이 경우 세상의 만능 정답인 적절한 상황에서 적절하게 사용하는게 가장 좋은 방법이라 할 수 있습니다.(?)
해당 예제는 https://github.com/sungwookkim/spring-designpattern 의 com.designPattern.creationalPattern.pattern.abstractFactory 패키지에서 확인하실 수 있습니다.
끝으로 마지막까지 긴 글을 읽어주셔서 감사하고 하시는 모든 코딩에 좋은 결과만이 있길 기도드리겠습니다. 감사합니다.