정적 팩토리 메소드

SeokHwan An·2023년 2월 23일
0

java

목록 보기
3/10

정적 팩토리 메서드

우아한테크코스 사다리 미션 중 controller에서 사다리의 한 줄을 생성하는 기능이 private로 담당하고 있었다.

public class Controller() {
		...
		private Line generateLine(List<String> playerNames) {
				// 사다리 한 라인을 생성하는 로직
		}
}

이와 같이 메소드를 작성하니 문제가 존재했다. 바로 Line 생성에 대한 테스트가 콘솔을 이용한 테스트 말고는 방법이 없었고 실제로도 오류가 많이 발생했다. 이를 통해 Line을 생성하는 기능에 대한 테스트 코드의 필요성을 느꼈고 이를 Line 객체의 부 생성자를 활용해 문제를 해결해 보라는 피드백을 받았으나 주 생성자와 부 생성자 모두 List를 인자로 받아 오버로딩이 불가능해 부 생성자로는 현 상황을 해결하는 것이 불가능했다. 다른 해결책으로 정적 팩토리 메소드를 이용하는 방안을 제시받았고 이번 기회에 정적 팩토리 메서드에 대해 학습을 해보려고 한다.

정적 팩토리 메서드란?


정적(static) 팩토리(factory) 메소드(method)는 객체 생성의 역할을 하는 클래스 메서드이다.

팩토리(factory)는 GoF 디자인 패턴 중 팩토리 패턴에서 유래되었고 객체를 생성하는 역할을 분리하겠다는 의미를 담고 있다

우리는 기본적으로 객체를 생성하는 역할로 항상 생성자를 생각해 왔을 것이다. 나 역시 여러 미션이나 프로젝트를 진행하면서 정적 메서드를 통해 객체를 생성하는 방안을 고민해본 적이 크게 없는 것 같다. java 공부를 하면 한번쯤 들어볼만한 책인 이팩티브 자바에서도 가장 먼저 다루는 아이템인 만큼 정적 팩토리 매서드는 생성자가 가지지 못하는 특별한 강점이 있는 것 같다.

정적 팩토리 메서드의 장점


1. 이름을 가질 수 있다.

정적 팩토리 메서드는 생성자와 달리 이름을 정할 수 있다. (사다리 미션 코드의 일부 입니다.)

public class Line {

		private List<Block> blocks;

		// 생성자
		public Line(Players players, List<Block> blocks) {
				...
        this.blocks = blocks;
    }
		
		// 정적 팩토리 메서드
		public static Line generateLine(BooleanGenerator booleanGenerator, Players players) {
				...
        return new Line(players, blocks);
    }
}

위의 코드를 보면 어떤 것이 느껴지나요? 아무런 느낌이 안들 수 있고 “둘다 Line 객체를 생성하는 기능을 하네”라고 생각할 수 있다. 하지만 여기서는 큰 차이가 있다. 생성자는 오버로딩을 통해 여러개가 만들어질 수 있지만 모두 public Line(…) 의 형태로 객체의 특성을 잘 설명하지 못한다. 하지만 정적 팩토리 메소드는 다르다. generateLine이라는 명칭을 통해 우리는 어떤 일이 발생하는 예측이 가능하다.

이렇듯 메소드에 이름을 넣을 수 있는 것 만큼 가독성이 높아지는 장점이 있습니다.

2. 인스턴스를 미리 생성하여 인스턴스 생성비용을 줄일 수 있다.

인스턴스를 미리 생성하여 인스턴스 생성비용을 줄일 수 있다 라는 말을 이해하기 위해서는 static이 어떻게 작동되는지 알아야한다. static 변수와 메소드는 컴파일 시 JVM의 heap 메모리에 저장되어 사용이 가능하다. 이 말을 잘 생각하면 static으로 인스턴스를 생성해 놓으면 인스턴스를 계속 생성할 필요없이 인스턴스를 구현해 활용할 수 있습니다. 코드를 보면 더 쉽게 이해가 가능할 것 입니다.

import java.util.HashMap;
public class DiceNumber {
    private static final int DICE_NUMBER_LOWER_BOUND = 1;
    private static final int DICE_NUMBER_UPPER_BOUND = 6;

    private static final Map<Integer, DiceNumber> CACHE = new HashMap<>();
    private static final String INVALID_NUMBER_MESSAGE = " 1 ~ 6 사이의 값을 입력해주세요.";
    private final Integer number;

    static {
        for (int i = DICE_NUMBER_LOWER_BOUND; i <= DICE_NUMBER_UPPER_BOUND; i++) {
            CACHE.put(i, new DiceNumber(i));
        }
    }

    private DiceNumber(Integer number) {
        this.number = number;
    }

    public static DiceNumber valueOf(Integer number) {
        validate(number);
        return CACHE.get(number);
    }

    private static void validate(Integer number) {
        if (number < DICE_NUMBER_LOWER_BOUND || DICE_NUMBER_UPPER_BOUND < number) {
            throw new IllegalArgumentException(INVALID_NUMBER_MESSAGE);
        }
    }
}

위의 코드는 주사위 숫자(1부터 6까지)를 VO(Value Object)로 Map에 저장해두고 정적팩토리 메소드를 통해 원하는 숫자의 인스턴스를 불러와 사용할 수 있다. 이 부분에서 얻을 수 있는 장점은 다음과 같다. 주사위 숫자의 경우 정해져 있는데 (1부터 6까지의 수) 이를 계속 새로운 인스턴스로 생성하는 것은 생산적인 측면에서 좋지 못하고 같은 값을 가진 객체를 계속 호출하는 것 역시 흐름에 맞지 않다. 그렇기에 미리 객체를 캐싱해 놓고 필요할 객체만을 불러서 활용하는 것이 정적팩토리 메소드의 장점이라고 할 수 있다.

3. 반환 타입의 하위 타입 객체를 반환 가능하며 입력 매개 변수에 따라 매번 다른 클래스를 반환이 가능하다.

정적팩토리 메소드는 자신을 상속 받는 하위 타임의 객체를 반환할 수 있다. 이는 큰 유연성을 가져다 주는데 간단하게 예를 들면 이동수단이라는 클래스를 상속받는 자동차, 기차, 비행기가 있다고 하자 시간 조건에 따라 원하는 객체를 반환해야할 때 우리는 따로 static 클래스를 선언할 필요 없이 자신의 하위 타입의 클래스를 반환이 가능하다.

public class Transpotation {

		public static Transpotation decideTranspotation(int time) {
				if (time > 20) {
						return new boat();
				}
				if (time > 15) {
						return new Car();
				}
				return new AirPlane();
 		}
}

이로인해 정적 팩토리 메소드는 생성자와는 다르게 유연성을 갖출 수 있고 다양한 상황에서 원하는 객체를 반환할 수 있다는 장점이 있습니다.

생각해 보아야 할 것


정적 팩토리 메소드이 무엇이고 어떤 장점을 가지고 있는지 알아보았는데 과연 어떤 상황에서 정적팩토리를 활용하는 것이 생성자를 이용하는 것보다 더 좋은 효과를 가져오는지 잘 생각해보면 좋을 거 같다.

0개의 댓글