오늘은 정적 팩토리 메소드에 대해서 알아보겠습니다 🎒
정적(static) + 팩토리(factory) + 메소드(method)
이름에서 알 수 있듯이, 정적 팩토리 메소드는 객체를 공장 처럼 찍어내는 역할을 하는 메소드를 말합니다 👨💻
일반적으로 new 키워드를 이용해서 객체를 생성하는 기존 방식과는 달리 정적 팩토리 메소드를 사용하면, 객체 생성의 역할을 분리할 수 있습니다.
실제 객체를 생성할 때 생성자를 이용한 생성보다는 정적 팩토리 메소드를 이용하는 것을 더 권장되고 있습니다.
우리는 String 클래스에서 정적 팩토리 메소드의 예시를 쉽게 찾아볼 수 있습니다.
String s1 = new String("생성자");
String s2 = String.valueOf("정적 팩토리 메소드");
String(문자열) 객체는 생성자 뿐만 아니라 valueOf 메소드를 이용해서 생성할 수 있습니다.
해당 메소드는 문자열의 정적 팩토리 메소드 예시라고 볼 수 있습니다.
이제부터는 생성자를 이용한 객체 생성과 정적 팩토리 메소드를 이용한 객체 생성을 좀 더 구체적으로 비교해보겠습니다. 🧑🏼💻
생성자라는 방식이 이미 존재하는데 왜 정적 팩토리 메소드 방식이 탄생한걸까요?
그리고 왜 정적 팩토리 메소드가 더 권장되는 것일까요?
여기에는 4가지 이유가 존재합니다.
이해를 위해 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 라는 점입니다.
생성자(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 객체를 생성할 필요없이 손쉽게 캐싱해올 수 있습니다 💪
생성자를 이용해서 객체를 생성한다면 해당 자료형의 객체만 생성할 수 있습니다.
하지만 정적 팩토리 메소드를 사용한다면 타입을 상속하는 자료형 객체 또한 반환할 수 있습니다.
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) 에 따라 하위 타입을 반환하는 정적 팩토리 메소드 예시입니다.
이처럼 정적 팩토리 메소드를 통해 생성자 방식과는 다르게 유연한 객체 생성이 가능합니다 🎵
들어가기에 앞서 캡슐화(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());
생성자를 사용해서 변환시 외부에서 생성자의 내부 구현을 모두 알 수 있습니다.
이는 캡슐화를 보장하지 않는 방식이며 객체 지향적인 프로그래밍 조건에 부합하지 않습니다 😢
이처럼 정적 팩토리 메소드를 통해 생성자의 역할을 할 수 있지만
개발자에게 좀 더 가독성있는 코드를 작성할 수 있게 해주며, 객체지향적으로 프로그래밍할 수 있도록 도와줍니다.
제 개인적인 생각이지만, 개발에 있어 어떠한 것도 완벽한 기능을 구현하는 것은 없다고 생각합니다. 즉 단점이 존재한다는 것이죠 ❗️
정적 팩토리 메소드도 단점이 존재합니다.
개발자가 임의로 만든 정적 팩토리 메소드는 특성 상, 다른 개발자들이 사용하기가 어렵다는 단점이 있습니다.
하지만 이러한 단점을 해결하기 위해 암묵적으로 정적 팩토리 메소드 네이밍 컨벤션
이 존재합니다 💪
정적 팩토리 메서드(Static Factory Method)
정적 팩토리 메서드(Static Factory Method)는 왜 사용할까?