[디자인 패턴] 정적 팩토리 메소드란?

최동근·2023년 1월 14일
0

디자인패턴

목록 보기
3/4
post-thumbnail

오늘은 정적 팩토리 메소드에 대해서 알아보겠습니다 🎒

🏭 정적 팩토리 메소드

정적(static) + 팩토리(factory) + 메소드(method)

이름에서 알 수 있듯이, 정적 팩토리 메소드는 객체를 공장 처럼 찍어내는 역할을 하는 메소드를 말합니다 👨‍💻
일반적으로 new 키워드를 이용해서 객체를 생성하는 기존 방식과는 달리 정적 팩토리 메소드를 사용하면, 객체 생성의 역할을 분리할 수 있습니다.
실제 객체를 생성할 때 생성자를 이용한 생성보다는 정적 팩토리 메소드를 이용하는 것을 더 권장되고 있습니다.
우리는 String 클래스에서 정적 팩토리 메소드의 예시를 쉽게 찾아볼 수 있습니다.

String s1 = new String("생성자");
String s2 = String.valueOf("정적 팩토리 메소드");

String(문자열) 객체는 생성자 뿐만 아니라 valueOf 메소드를 이용해서 생성할 수 있습니다.
해당 메소드는 문자열의 정적 팩토리 메소드 예시라고 볼 수 있습니다.
이제부터는 생성자를 이용한 객체 생성과 정적 팩토리 메소드를 이용한 객체 생성을 좀 더 구체적으로 비교해보겠습니다. 🧑🏼‍💻

🏭 생성자보다는 정적 팩토리 메소드

생성자라는 방식이 이미 존재하는데 왜 정적 팩토리 메소드 방식이 탄생한걸까요?
그리고 왜 정적 팩토리 메소드가 더 권장되는 것일까요?
여기에는 4가지 이유가 존재합니다.

  • 메소드의 이름을 통해 가독성을 높일 수 있습니다.
  • 호출 할 때마다 새로운 객체를 생성할 필요가 없습니다.
  • 하위 자료형 객체를 반환할 수 있습니다.
  • 객체 생성을 캡슐화 할 수 있습니다.

1. 메소드의 이름을 통해 가독성을 높일 수 있습니다.

이해를 위해 Car 클래스 코드를 통해 두 방법을 비교해보겠습니다.

// 생성자를 이용한 객체 생성
public class Car {

    private final String name;

    private final boolean electricity;


    public Car(String name, boolean electricity) {

        this.name = name;
        this.electricity = electricity;
    } // 전기차 x

    public Car(String name) {

        this.name = name;
        this.electricity = false;
    } // 전기차 o
}
// 정적 팩토리 메소드를 이용한 객체 생성
public class Car {

    private final String name;

    private final boolean electricity;


    private Car(String name, boolean electricity) {

        this.name = name;
        this.electricity = electricity;
    } // 전기차 x

    private Car(String name) {

        this.name = name;
        this.electricity = false;
    } // 전기차 o

    public static Car createElectricityCar(String name, boolean electricity) {

        return new Car(name, electricity);
    }

    public static Car createCar(String name) {

        return new Car(name);
    }
}
// 객체 생성 호출
// 생성자(new)를 이용한 객체 생성
public static void main(String[] args ){

	Car electricityCar = new Car("테슬라", true);
    Car car = new Car("아반떼"); 
    
}

// 정적 팩토리 메소드를 이용한 객체 생성
public static void main(String[] args) {

	Car electricityCar = Car.createElectricityCar("테슬라", true);
    Car car = Car.createCar("아반떼");
    
}

해당 코드는 Car 객체에 대해 생성자를 이용한 방법과 정적 팩토리 메소드를 이용한 방식을 비교합니다.
일반적인 생성자(new) 를 이용해서 객체를 생성할때 보다 정적 팩토리 메소드를 이용해서 객체를 생성할때 메소드 이름을 통해 어떠한 객체가 반환되는지 유추 할 수 있습니다.
즉 코드의 가독성을 높여주고 생산성을 높여줍니다 💪
또한 여기서 주목해야할 점은 생성자의 접근 제어 지시자는 private 라는 점입니다.

2. 호출 할 때마다 새로운 객체를 생성할 필요가 없습니다.

생성자(new)를 사용할 때에는 필요한 객체가 필요할 때마다 생성을 해야합니다.
하지만 정적 팩토리 메소드를 이용하면 캐싱 기법을 이용해서 객체를 새로 만들어 줄 필요 없이
미리 생성해 놓은 객체를 가져다 사용할 수 있습니다 👨‍💻

예제 코드를 통해 확인해보겠습니다.

// Caching + Static Factory Method
public class Test {

    private static final int MIN_LOTTO_NUMBER = 1;

    private static final int MAX_LOTTO_NUMBER = 45;

    private static Map<Integer, Test> lottoNumberCache
            = new HashMap<>();

    static {

        IntStream.range(MIN_LOTTO_NUMBER, MAX_LOTTO_NUMBER)
                .forEach(i -> lottoNumberCache.put(i,new Test(i)));
    } // 45 번 lottoNumberCache 에 값 넣음(클래스 생성시 같이 생성)

    private int number;

    private Test(int number) {

        this.number = number;

    } // 생성자의 접근 제어 지시자 => private


    public Test of( int number ){

        return lottoNumberCache.get(number); // Lotto Number 를 반환화는 정적 팩토리 메소드

    } // number 해당하는 Test 객체 리턴

}

Test 클래스는 로또 생성과 관련된 클래스입니다.
주목해야할 부분은 of 메소드입니다. number 의 int 값을 매개변수로 전달하면
Test 객체를 반환하는데 해당 객체는 이미 저장되어 있는(= 캐싱되어있는) 객체입니다.
따라서 새로 Test 객체를 생성할 필요없이 손쉽게 캐싱해올 수 있습니다 💪

3. 하위 자료형 객체를 반환할 수 있습니다.

생성자를 이용해서 객체를 생성한다면 해당 자료형의 객체만 생성할 수 있습니다.
하지만 정적 팩토리 메소드를 사용한다면 타입을 상속하는 자료형 객체 또한 반환할 수 있습니다.
Music 클래스를 상속하는 Ballad,HipHop,Pop 클래스로 예시를 들어보겠습니다.

public class Music {

	public static Music of(int bpm) { // Music 클래스의 정적 팩토리 메소드
    
    	if( bpm < 30 ) {
        	return new Ballad();
        } else if (bpm < 90 ) {
        	return new Pop(); {
        } else { 
        	return new HipHiop();
        }
  }

해당 코드는 bpm(beats per minute) 에 따라 하위 타입을 반환하는 정적 팩토리 메소드 예시입니다.
이처럼 정적 팩토리 메소드를 통해 생성자 방식과는 다르게 유연한 객체 생성이 가능합니다 🎵

4. 객체 생성을 캡슐화할 수 있습니다.

들어가기에 앞서 캡슐화(Encapsulation) 에 대해 알아보겠습니다.

캡슐화란 데이터의 은닉을 말합니다.
즉 해당 기능을 외부로 노출시키지 않는 기법이라고 생각하면 됩니다.
객체 지향적 프로그래밍에서 가장 중요한 특징입니다.

정적 팩토리 메소드를 통해 캡슐화를 할 수 있습니다.
DTO(Data Transfer Object)는 Entity 대신 계층 간에 데이터를 전송하기 위해 사용되어지는 객체입니다.
코드 예시로 DTO 관련 정적 팩토리 메소드를 들어보겠습니다 🧑🏼‍💻

public class Dto {

	private String name;
    private Long value;
    
    public static Dto from(GivenEntity entity) {
    
    	return new Dto(entity.getName(), entity.getValue());
        
    } 
}

Dto dto = Dto.from(entity); // 정적 팩토리 메소드를 통해 dto 로 변환

해당 코드는 Dto 클래스의 from 메소드를 통해 Entity를 Dto 로 변환하는 예시입니다.
어떤가요? 어떤 방법으로 변환되는지 전혀 알 수 없죠? 단지 entity 변수를 매개변수로 넘겨주는 것만 확인할 수 있습니다.
반면 생성자 방식으로 변환한다면 어떨까요? 🤔

Dto dto = new Dto(entity.getName(), entity.getValue());

생성자를 사용해서 변환시 외부에서 생성자의 내부 구현을 모두 알 수 있습니다.
이는 캡슐화를 보장하지 않는 방식이며 객체 지향적인 프로그래밍 조건에 부합하지 않습니다 😢

이처럼 정적 팩토리 메소드를 통해 생성자의 역할을 할 수 있지만
개발자에게 좀 더 가독성있는 코드를 작성할 수 있게 해주며, 객체지향적으로 프로그래밍할 수 있도록 도와줍니다.

🏭 정적 팩토리 메소드의 단점

제 개인적인 생각이지만, 개발에 있어 어떠한 것도 완벽한 기능을 구현하는 것은 없다고 생각합니다. 즉 단점이 존재한다는 것이죠 ❗️
정적 팩토리 메소드도 단점이 존재합니다.

1. 상속에는 public 혹은 protected 생성자가 필요하므로 정적 팩토리 메소드만 제공할 경우, 상속이 불가능합니다.

2. 정적 팩토리 메소드를 다른 개발자들이 이용하기 어려울 수 있습니다.

개발자가 임의로 만든 정적 팩토리 메소드는 특성 상, 다른 개발자들이 사용하기가 어렵다는 단점이 있습니다.
하지만 이러한 단점을 해결하기 위해 암묵적으로 정적 팩토리 메소드 네이밍 컨벤션이 존재합니다 💪

정적 팩토리 메소드 네이밍 컨벤션

  • from : 하나의 매개변수를 받아 해당 타입의 객체를 반환하는 메소드
  • of : 여라 매개변수를 받아 적합한 타입의 객체를 반환하는 메소드
  • valueOf : from & of 랑 비슷하지만 좀 더 자세한 버전
  • instance OR getInstance : 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스 보장은 하지 않음
  • create OR newInstance : 새로운 인스턴스를 생성함을 보장

참고

정적 팩토리 메서드(Static Factory Method)
정적 팩토리 메서드(Static Factory Method)는 왜 사용할까?

profile
비즈니스가치를추구하는개발자

0개의 댓글