정적 팩토리 메서드는 생성자 대신 다양한 장점을 제공할 수 있다.
💡여기서 말하는 정적 팩토리 메서드는 디자인 패턴에 나오는 팩토리 메서드와 다른것이다.
디자인 패턴에서 정적 팩토리 메서드와 관련된 패턴은 없다.
장점
장점 1. 이름을 가질 수 있다.
public class Person{
private String name;
private Person(String name){ // 생성자를 private으로 제한
this.name = name;
}
public static Person from(String name){ // from이라는 이름을 가짐
return new Person(name);
}
}
위처럼 생성자는 private으로 두면 클라이언트가 정적 팩토리 메서드를 사용하도록 유도할 수 있다.
위 정적 팩토리 메서드 예제는 인자를 하나만 받고있기 때문에 네이밍을 from으로 설정했다.
장점 2. 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다.
public class CachingInstance{
private static final Map<Integer, CachingInstance> CACHE_MAP = new HashMap<>();
private final int key;
private CachingInstance(int key){ // 생성자 private으로 제한
this.key = key;
}
public static cachingInstance instance(int key){
if(!CACHE_MAP.containskey(key){
CACHE_MAP.put(key,new CachingInstance(key));
}
return CACHE_MAP.get(key);
}
캐시기능을 구현하기 위해 Map을 만들고, 정적 팩토리 메서드 내에서 들어온 key값에 따라 key값이 동일하다면 기존에 만들어진 인스턴스를 반환하고, 없다면 새로 만들어서 반환한다.
public static void main(String[] args){
CachingInstance instance1 = CachingInstance.instance(1);
CachingInstance instance2 = CachingInstance.instance(2);
CachingInstance instance3 = CachingInstance.instance(1);
CachingInstance instance4 = CachingInstance.instance(2);
System.out.println(instance1.equals(instance3)); // true
System.out.println(instance2.equals(instance4)); // true
System.out.println(instance1.equals(instance2)); // false
}
인스턴스 1,3과 2,4는 서로 같은 인스턴스인것을 확인할 수 있다.
장점3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
public interface Car {
void drive();
static Sedan createSedan(){
return new Sedan();
}
static SUV createSUV(){
return new SUV();
}
}
class Sedan implements Car {
@Override
public void drive() {
System.out.println("Driving a Sedan!");
}
public Sedan(){
}
}
class SUV implements Car {
@Override
public void drive() {
System.out.println("Driving an SUV!");
}
}
Sedan과 SUV클래스는 Car 인터페이스를 확장하고 있다.
인터페이스에선 생성자를 만들수없기에, 정적 팩토리 메서드를 이용해서 각 하위객체로 리턴하게 하는 것이다.
public static void main(String[] args) {
Car sedan = Car.createSedan();
Car suv = Car.createSUV();
sedan.drive(); // Driving a Sedan!
suv.drive(); // Driving an SUV!
}
따라서 개발자는 Sedan과 SUV의 구현체를 찾아볼 필요가 없게되고, API는 경량화 된다.
장점4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
이는 3번과 유사한데, 3번의 Car에 새로운 정적 팩토리 메서드를 하나 생성해보자.
public interface Car {
void drive();
static Sedan createSedan(){
return new Sedan();
}
static SUV createSUV(){
return new SUV();
}
static Car fromCost(Long cost){ // 새로운 정적 팩토리 메서드 추가
if(cost<9000L){
return new Sedan();
}
else if(cost>9000L && cost<12000L){
return new SUV();
}
else{
return new Van();
}
}
}
fromCost()는 들어온 매개변수 cost에 따라 다른 객체를 반환하는 것을 확인할 수 있다.
장점5. 정적 팩토리 메서드를 작성하는 시점에서 반환할 객체의 클래스가 존재하지 않아도 된다.
사실 "반환할 객체의 클래스가 존재하지 않아도 된다."라는 말이 무슨말인지 잘 이해가 안가서 GPT한테 물어봤다.
GPT는 "정적 팩토리 메서드를 정의할 때 특정 클래스의 구현이 반드시 필요하지 않다"라고 답변을 줬는데,
"존재하지 않아도 된다"와 "존재하지만 구현이 필요하지 않다"는 다른 의미인 것 같아서 여러 블로그를 뒤져보았으나 딱히 이해가는 글은 찾지 못 했다. 이는 나중에 따로 찾아서 이해가 필요한 부분인 것 같다.
단점
단점1. 정적 팩토리 메서드를 구현한 클래스는 상속이 불가능
이 챕터는 "생성자 대신 정적 팩토리 메서드"를 사용하는 것에 초점이 맞춰져 있다.
따라서 정적 팩토리 메서드를 사용하도록 유도하기 위해 부모의 기본 생성자를 private으로 설정하게 되면
당연히, 자식 클래스는 부모의 생성자를 사용할 수 없기때문에, 상속이 불가능하다.
하지만 이는 더 좋은 방향으로 유도될 수 있는데, 컴포지션을 이용하도록 유도될 수 있고, '불변 타입'을 지키도록 유도할 수 있다.
컴포지션은 뒷편에 나오는 부분이기에 간단한 예제를 작성했다.
public class Book {
private final String title;
private Book(String title) {
this.title = title;
}
public static Book create(String title) {
return new Book(title);
}
public String description() {
return "Book Title: " + title;
}
}
class Library {
private final Book book;
public Library() {
this.book = Book.create("The Great Gatsby");
}
public String description() {
return book.description();
}
}
public class Main {
public static void main(String[] args) {
Library library = new Library();
System.out.println(library.description()); // 출력: Book Title: The Great Gatsby
}
}
단점2. 정적 팩토리 메서드는 프로그래머가 찾기 어렵다.
생성자는 API 문서에서 따로 구분되어 있지만, 정적 팩토리 메서드는 결국 메서드중 하나이기 때문에 다른 메서드들과 같이 분류되어서 찾기 어렵다.
따라서 이를 조금이나마 해결하고자 개발자들간의 규약을 정해놓았다.
정적 팩토리 메서드의 네이밍 규약
메서드명 | 설명 |
from | 하나의 매개변수를 받아 해당 타입의 인스턴스를 반환하는 형변환 메서드 |
of | 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드 |
valueOf | from과 of의 더 자세한 버전 |
instance / getInstance | 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지 않음 |
create / newInstance | instance 또는 getInstance와 같으나, 매번 새로운 인스턴스를 생성해 반환함을 보장 |
getType | getInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩토리 메서드를 정의할 때 사용. “Type”은 팩토리 메서드가 반환할 객체의 타입을 의미 |
newType | newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩토리 메서드를 정의할 때 사용. “Type”은 팩토리 메서드가 반환할 객체의 타입을 의미 |
type | getType과 newType의 간결한 버전 |
'Java' 카테고리의 다른 글
Java) 인스턴스화를 막으려거든 private 생성자를 사용하라 (3) | 2024.11.07 |
---|---|
Java) 생성자나 열거 타입으로 싱글턴임을 보증하라 (1) | 2024.11.05 |
Java) 생성자에 매개변수가 많다면 빌더를 고려하라 (4) | 2024.11.04 |