오늘 알아볼 주제는 "생성자 대신 팩터리 메서드를 고려하라"입니다.
왜 생성자 대신 팩터리 메서드를 고려해야 할까요? 먼저 정적 팩터리 메서드가 생성자보다 좋은 장점 5가지에 대해 알아보도록 하겠습니다.
장점
1. 이름을 가질 수 있습니다.
2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 됩니다.
3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있습니다.
4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있습니다.
5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 됩니다.
장점이 있으면 단점도 늘 있는법 단점에 대해서도 알아보도록 하겠습니다.
단점
1. 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없습니다.
2. 정적 펙터리 메서드는 프로그래머가 찾기 어렵습니다.
이러한 단점들이 존재합니다.
다음으로 정적 팩터리 메서드에서 사용되는 명명 방식들에 대해 알아보도록 하겠습니다.
정적 팩터리 메서드에 흔히 사용하는 명명 방식
from
매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드입니다.
Date d = Date.from(instant);
of
여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드입니다.
Set faceCards = EnumSet.of(JACK,QUEEN,KING);핵심 정리
valueof
from과 of의 더 자세한 버전
ex) BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
instance or getInstance
매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지 않습니다.
ex) StackWalker luke = StackWalker.getInstance(options);
create 혹은 newInstance
instance 혹은 getInstance와 같지만, 매번 새로운 인스턴스를 생성해 반환함을 보장합니다.
getType
getInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 사용합니다.
"Type"은 팩터리 메서드가 반환할 객체의 타입입니다.
ex) FileStore fs = Files.getFileStore(path)
newType
newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 씁니다.
"Type"은 팩터리 메서드가 반환할 객체의 타입입니다. ex) BufferedReader br = Files.newBufferedReader(path);
type: getType과 newType의 간결한 버전입니다.
ex) List litany = Collections.list(legacyLitany);
정적 팩터리 메서드는 코드로 어떻게 사용이 되는지 알아보기 위해, 직접 작성한 코드로 살펴보도록 하겠습니다.
public class User {
private String username;
private String email;
private boolean isAdmin;
// private 생성자
private User(String username, String email, boolean isAdmin) {
this.username = username;
this.email = email;
this.isAdmin = isAdmin;
}
// 정적 팩토리 메서드 - 일반 사용자 생성
public static User createNormalUser(String username, String email) {
return new User(username, email, false); // 일반 사용자
}
// 정적 팩토리 메서드 - 관리자 생성
public static User createAdmin(String username, String email) {
return new User(username, email, true); // 관리자
}
// 정적 팩토리 메서드 - 익명 사용자 생성
public static User createAnonymousUser() {
return new User("Anonymous", "unknown@example.com", false);
}
// toString 메서드 (객체 정보를 출력하기 위함)
@Override
public String toString() {
return String.format("User [username=%s, email=%s, isAdmin=%s]",
username, email, isAdmin ? "Admin" : "Normal");
}
}
생성자에 private 접근 제어자를 사용해서 외부에서 인스턴스를 생성할 때, 생성자를 호출하지 못하도록 막아주었습니다.
앞서 정리한 장점 중 "이름을 가질 수 있습니다"와 "입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있습니다."를 충족시키는 코드임을 알 수 있습니다.
Main 클래스에서 사용하는 코드는 다음과 같습니다.
public class Main {
public static void main(String[] args) {
// 일반 사용자 생성
User normalUser = User.createNormalUser("JohnDoe", "john.doe@example.com");
// 관리자 생성
User adminUser = User.createAdmin("AdminUser", "admin@example.com");
// 익명 사용자 생성
User anonymousUser = User.createAnonymousUser();
// 출력
System.out.println(normalUser); // User [username=JohnDoe, email=john.doe@example.com, isAdmin=Normal]
System.out.println(adminUser); // User [username=AdminUser, email=admin@example.com, isAdmin=Admin]
System.out.println(anonymousUser); // User [username=Anonymous, email=unknown@example.com, isAdmin=Normal]
}
}
핵심 정리
정적 팩터리 메서드와 public 생성자는 각자의 쓰임새가 있으니 상대적인 장단점을 이해하고 사용하는 것이 좋습니다. 그렇다고 하더라도 정적 팩터리를 사용하는 게 유리한 경우가 더 많으므로 무작정 public 생성자를 제공하는 것은 좋지 않은 습관이므로, 클래스를 생성할 때 고려해봐야 하는 사항입니다.
'Java' 카테고리의 다른 글
immutable 객체로 만드는 방법 (0) | 2025.01.09 |
---|---|
JVM 정복하기 (0) | 2025.01.09 |
자바 메모리 구조 (0) | 2024.10.02 |
Objects.equals() vs equals() (0) | 2024.09.25 |
Object.equals() 메서드가 정말 null-safe한가? (0) | 2024.09.25 |