본문 바로가기
ETC/도서

추상팩토리 패턴을 사용이유

by sorryisme 2022. 12. 16.

제목과 다르게 추상팩토리 패턴를 왜 쓰는지에 대한 의문점이라기 보다 Clean Code 내용을 보던 중 공감가는 부분이 추상팩토리 패턴과 연관있어 작성하게 되었다. Clean Code 3장 함수 부분을 보면 Switch에 대한 본질적인 문제점과 이를 최대한 해소하기 위한 노력을 보여준다

실제로 Switch 코드를 수정하다 보면 여러 수정을 손대야하는 경우가 많아서 이에 대한 내용을 짧게 정리하고자 한다.

출처 : Clean Code 3장 내용 일부 발췌

 

 

public Money calculatePay(Employee e) throws InvalidEmployeeType {
	switch(e.type){
        case COMMISSIONED :
            return calculateCommissionedPay(e);
        case HOURLY:
            return calculateHourlyPay(e);
        case SALARIED:
            return calculateSalariedPay(e);
        default :
            throw new InvalidEmployeeType(e.type);
    }
}

그냥 평범한 Switch 문이고 소스코드 상에서 흔히 보이는 소스이지만 Clean Code에서 다음과 같이 문제를 제기한다

 

문제점

  1. 함수가 길다 > 새 직원 유형 추가하면 더 길어진다
  2. ‘한 가지’ 작업만 수행하지 않는다.
  3. SRP를 위반 > 코드를 변경할 이유가 여럿이기 때문이다
  4. OCP 위반 > 새 직원을 추가할 때 마다 코드를 변경
  5. 위 함수 구조와 동일한 함수가 존재한다는 사실

 

Clean code는 5번이 가장 심각하다고 판단한다. 책에서는 예제 코드를 포함하지 않았지만 예측해보면 다음과 같다고본다.

 
public boolean isPayday(Employee e, Date date) throws InvalidEmployeeType {
	switch(e.type){
        case COMMISSIONED :
           return date.getDate() == 30 ? true : false;
        case HOURLY:
        	return date.getDate() == 10 ? true : false;
        case SALARIED:
			return date.getDate() == 5 ? true : false;
    	default :
            throw new InvalidEmployeeType(e.type);
    }
}
  • 물론 date.getDate는 deprecated되었지만 예시 코드이기에 그냥 작성했다.

 

위와 같은 함수는 무한정 생성될 것이며 또 추가될 것이다. 새로운 유형의 고객이 추가 될 때마다 Switch 구문에 새로운 코드를 넣어줘야하는 엄청난 작업이 발생될 것이다.

그리고 실제로 나는 이런 코드를 만났으며 새로운 유형이 추가될 때마다 Switch문을 찾아다닌다. 또 수정이 발생되면 모든 Switch문을 뒤적거린다.그로인해 switch문에 대한 불신이 발생했지만 어떻게 해소할 방안을 찾지 못하였다. Clean Code는 이런 문제를 해결할 방안으로 추상 팩토리를 제안한다

 

 

public abstract class Employee{
    public abstract boolean isPayday();
    public abstract Money calculatePay();
    public abstract void delivery(Money pay);
}

public interface EmployeeFactroy{
    public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType;
}

public class EmployeeFactoryImpl implements EmployeeFactroy {
    public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType{
        switch(r.type){
            case COMISSIONED :
                return new CommissionedEmployer(r);
            case HOURL:
                return new HourlyEmployee(r);
            case SALARIED:
                return new SalariedEmpoyee(r);
            default: 
                throw new InvalidEmployeeType(r.Type);
        }
    }
}

EmployeeFactory의 구현체는 Type별로 객체를 새로 생성해준다. 중요한 것은 생성된 객체들은 추상 클래스의

파생 클래스라는 점이다. 추상클래스의 메소드를 호출하면 추상클래스를 상속받은 클래스의 메소드들이 호출될 것이다.

해소된 사항은 다음과 같다.

1) 이전처럼 switch문을 찾아다니며 내용을 수정할 필요가 없다. 수정할 클래스를 찾아가 그 부분만 수정하면 된다.

2) 만약 이전 방식이였다면 새로운 유형의 직원이 추가되었을 때 모든 switch문에 추가추가추가…하지만 switch에 새로운 클래스를 생성해서 return 해주면 되고 나머지는 추상클래스를 상속받아 메소드를 구현하기만 하면된다.

 

결론

switch문을 대량 생산하기 전 또는 기존에 만들어놓은 switch문을 Abstract Factory Pattern을 고려해봐야한다

참고내용 : CleanCode 3장 함수 일부 발췌