Design Pattern/기타

정적 팩토리 메서드 패턴

객체 생성을 캡술화 하는 기법으로 좀더 구체적으로 객체를 생성하는 메서드를 만들고 static으로 선언하는 기법이다.

  • 장점
    1. 이름이 있으므로 생성자에 비해 가독성이 좋다.
    2. 호출할 때마다 새로운 객체를 생성할 필요가 없다.
    3. 하위 자료형 객체를 반환할 수 있다.
    4. 객체 생성을 캡슐화할 수 있다.
  • 단점
    1. 정적 팩토리 메서드만 있는 클래스라면, 생성자가 없으므로 하위 클래스를 못 만든다.
    2. 정적 팩토리 메서드는 다른 정적 메서드와 잘 구분되지 않는다.

위와 같은 장단점이 있는데 하나 씩 확인해보자.

 

1. 가독성

생성자와 비교했을 때 가독성이 높다는 것인데 비교를 위해서 아래 와 같은 클래스를 만들었습니다.

4개의 정적 팩토리 메서드 패턴을 만들었습니다.  

public class Character {
    int STR;
    int DEX;
    int INT;
    int LUK;

    public Character(int STR, int DEX, int INT, int LUK) {
        this.STR = STR;
        this.DEX = DEX;
        this.INT = INT;
        this.LUK = LUK;
    }

    public static Character newWarrior() {
        return new Character(15, 10,4,4);
    }

    public static Character newArcher() {
        return new Character(10, 15,4,4);
    }

    public static Character newMage() {
        return new Character(4, 4,15,10);
    }

    public static Character newThief() {
        return new Character(4, 10,4,15);
    }
}

 

전사, 궁수, 법사, 도적의 주스텟 부스텟 등을 반영한 캐릭터를 만들 때 생성자를 사용하는 경우와  정적 팩토리 메서드 패턴을 사용하는 경우를 보면 명확하게 가독성의 차이가 있다는 것을 알게 됩니다.

// 생성자
Character warrior1 = new Character(15, 10,4,4);
Character archer1 = new Character(10, 15,4,4);
Character mage1 = new Character(4, 4,15,10);
Character thief1 = new Character(4, 10,4,15);

// 정적 팩토리 메서드
Character warrior2 = Character.newWarrior();
Character archer2 = Character.newArcher();
Character mage2 = Character.newMage();
Character thief2 = Character.newThief();

 

2. 호출시 새로운 객체 생성  X

immutable 객체를 캐시해서 사용한 다면 굳이 일일이 new같은 연산을 사용할 필요가 없어집니다.

대표적인 예로 BigInteger가 있습니다.

사적에 0일 생성하고 valueOf 라는 정적 팩토리 메서드 패턴의 메서드를 이용하여 이를 리턴하게 됩니다.

public static final BigInteger ZERO = new BigInteger(new int[0], 0);

public static BigInteger valueOf(long val) {
        // If -MAX_CONSTANT < val < MAX_CONSTANT, return stashed constant
        if (val == 0)
            return ZERO;
        if (val > 0 && val <= MAX_CONSTANT)
            return posConst[(int) val];
        else if (val < 0 && val >= -MAX_CONSTANT)
            return negConst[(int) -val];

        return new BigInteger(val);
    }

 

3. 하위 자료형 객체를 유연하게 반환

고프의 팩토리 패턴과 유사하게 값을 입력 받아서 다르게 처리하는 개념이다.

class OrderUtil {

    public static Discount createDiscountItem(String discountCode) throws Exception {
        if(!isValidCode(discountCode)) {
            throw new Exception("잘못된 할인 코드");
        }
        // 쿠폰 코드인가? 포인트 코드인가?
        if(isUsableCoupon(discountCode)) {
            return new Coupon(1000);
        } else if(isUsablePoint(discountCode)) {
            return new Point(500);
        }
        throw new Exception("이미 사용한 코드");
    }
}

class Coupon extends Discount { }
class Point extends Discount { }

 

4. 객체 생성을 캡슐화

아래는 예시는 DTO와 Entity간에는 형 변환에 관한 부분으로

생성자를 사용하는 경우 외부에 생성자 내부 구현을 드러낸 채 사용한다면

정적 팩토리 메서드 패턴을 사용하면 내부 구현을 숨겨서 사용할 수 있다. 

public class CarDto {
  private String name;
  private int position;
  
  pulbic static CarDto from(Car car) {
    return new CarDto(car.getName(), car.getPosition());
  }
}


CarDto carDto = new CarDto(car.getName(), car.getPosition); // 생성자를 쓴 경우
Car carDto = CarDto.from(car); // 정적 팩토리 메서드를 쓴 경우

 

정적 팩토리 메서드 패턴의 네이밍 컨벤션

앞서 단점으로 정적 팩토리 메서드는 다른 정적 메서드와 잘 구분되지 않는다. 라고 하였는데 이 때문에 구분을 쉽게 하기 위해서 네이밍 컨벤션이 존재한다.

  • from : 하나의 매개 변수를 받아서 객체를 생성
  • of : 여러개의 매개 변수를 받아서 객체를 생성
  • getInstance | instance : 인스턴스를 생성. 이전에 반환했던 것과 같을 수 있음.
  • newInstance | create : 새로운 인스턴스를 생성
  • get[OtherType] : 다른 타입의 인스턴스를 생성. 이전에 반환했던 것과 같을 수 있음.
  • new[OtherType] : 다른 타입의 새로운 인스턴스를 생성.

ps

롬복에서는 아래처럼 정적 팩토리 메서드 패턴을 쉽게 만들 수 있다.

@RequiredArgsConstructor(staticName = "of")
public class BlogUser {
  private final Long id;
  private final String name;
}

 

Reference

https://johngrib.github.io/wiki/static-factory-method-pattern/

https://velog.io/@ljinsk3/%EC%A0%95%EC%A0%81-%ED%8C%A9%ED%86%A0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9C%EB%8A%94-%EC%99%9C-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C