직접 생성자(Constructor)를 사용해서 클래스의 객체를 만드는 것이 아닌, 별도로 public static 메서드를 만들어서 클래스의 객체를 생성하는 방법
정적 팩토리 메서드를 구성하고자 한다면, 반드시 생성자에 private 접근 제어자를 두어서 외부에서 객체를 만들 때 생성자가 아닌 public한 정적 팩토리 메서드를 사용하게끔 한다.
class Book {
private String title;
// 생성자를 private화 하여 외부에서 생성자 호출 차단
private Book(String title) { this.title = title; }
// 정적 팩토리 메서드
public static Book titleOf(String title) {
return new Book(title); // 메서드에서 생성자를 호출하고 리턴함
}
}
public static void main(String[] args) {
// 정적 메서드 호출을 통해 인스턴스화된 객체를 얻음
Book book1 = Book.titleOf("어린왕자");
}
public class Car {
private String name;
private String color = "white";
public Car(String name, String color) {
this.name = name;
this.color = color;
}
}
// 이렇게 사용하면 Car 클래스의 속성이 뭔지 알기 어렵고 객체 생성할 때도 어렵다.
Car myCar = new Car("SM5", "black");
// 정적 팩토리 메서드 사용 클래스
public class Car {
private String name;
private String color = "white";
public static Car nameColorOf(String name, String color) {
return new Car(name, color);
}
}
Car myCar = Car.nameColorOf("SM5", "black");
// 일반 클래스
class Day {
private String day;
public Day(String day) { this.day = day; }
public String getDay() { return day; }
}
// Day 객체를 생성하고 관리하는 Flyweight 팩토리 클래스
class DayFactory {
// Day 객체를 저장하는 캐싱 저장소 역할
private static final Map<String, Day> cache = new HashMap<>();
// 자주 사용될것 같은 Day 객체 몇가지를 미리 등록한다
static {
cache.put("Monday", new Day("Monday"));
cache.put("Tuesday", new Day("Tuesday"));
cache.put("Wednesday", new Day("Wednesday"));
}
// 정적 팩토리 메서드 (인스턴스에 대해 철저한 관리)
public static Day from(String day) {
if(cache.containsKey(day)) {
// 캐시 되어있으면 그대로 가져와 반환
System.out.println("해당 요일은 캐싱되어 있습니다.");
return cache.get(day);
} else {
// 캐시 되어 있지 않으면 새로 생성하고 캐싱하고 반환
System.out.println("해당 요일은 캐싱되어 있지 않아 새로 생성하였습니다.");
Day d = new Day(day);
cache.put(day, d);
return d;
}
}
}
public static void main(String[] args) {
// 이미 등록된 요일 가져오기
Day day = DayFactory.from("Monday");
System.out.println(day.getDay()); // 해당 요일은 캐싱되어 있습니다.
// 등록되지 않은 요일 가져오기
day = DayFactory.from("Friday");
System.out.println(day.getDay()); // 해당 요일은 캐싱되어 있지 않습니다.
}
// 자바 8부터는 인터페이스가 정적 메서드를 가질 수 있다.
class Kia implements Car {}
class BMW implements Car {}
interface Car {
public static Car getKia() {
return new Kia();
}
public static Car getBMW() {
return new BMW();
}
}
메서드 블록 내에서 분기문(if문)을 통해 여러 자식 타입의 객체를 반환할 수 있다.
위 3번의 확장된 개념이다.
interface Car {
public static Car getCar(String color) {
if(color == "black") {
return new Kia();
}
return new BMW();
}
}
생성자(Constructor)를 사용하는 경우 내부 구현을 외부로 노출시키는데, 정적 팩토리 메서드는 구현부를 외부로부터 숨길 수 있다.
의존성을 제거하기도 한다.
3번 개념을 확장한 개념
interface Grade {
String toText();
}
class A implements Grade {
@Override
public String toText() {return "A";}
}
class B implements Grade {
@Override
public String toText() {return "B";}
}
class C implements Grade {
@Override
public String toText() {return "C";}
}
class D implements Grade {
@Override
public String toText() {return "D";}
}
class F implements Grade {
@Override
public String toText() {return "F";}
}
class GradeCalculator {
// 정적 팩토리 메서드
public static Grade of(int score) {
if (score >= 90) {
return new A();
} else if (score >= 80) {
return new B();
} else if (score >= 70) {
return new C();
} else if (score >= 60) {
return new D();
} else {
return new F();
}
}
}
public static void main(String[] args) {
String myFriendScore = GradeCalculator.of(36).toText();
String myScore = GradeCalculator.of(99).toText();
System.out.println(myFriendScore); // F
System.out.println(myScore); // A
}
위 예제의 메인 메서드를 보면 GradeCalculator의 정적 팩토리 메서드 of() 를 호출하여 Grade 인터페이스 타입의 객체를 반환할 뿐이지, Grade 인터페이스의 구현체인 A ~ F 객체 존재에 대해서는 모르게 된다.
즉, 클라이언트는 구현체를 신경쓸 필요없이 제공되는 메서드를 호출만 하면 되어 편리하게 사용이 가능해진다.
상속을 하려면 public 혹은 protected 생성자가 필요하니 정적 팩토리 메서드만 제공하면 상속할 수 없다.
오히려 상속을 피한다는 점에서는 장점이 되기도 한다.
다른 개발자는 생성자를 사용한지 정적 팩토리 메서드를 사용한지 모르기 때문에 사용하기 어려울 수 있다.
따라서 정적 팩토리 메서드 이름도 널리 알려진 규약을 따라 지어줘야 한다.
매개 변수를 하나 받아서 해당 타입의 객체를 반환하는 형변환 메서드
Date d = Date.from(instant);
여러 개의 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드
Set<Car> cars = EnumSet.of(BMW, Kia, Hyndai)
from과 of의 더 자세한 버전
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);