인스턴스를 생성하기 위한 전통적인 방식은 생성자(constructor) 말고 다른 다른 방법이 있다. public static 팩토리 메소드(pulbic static factory method)를 사용해서 클래스 내에서 인스턴스를 반환하는 방법이다.
만약 생성자가 제공하는 파라미터들이 반환되는 인스턴스에 대해 설명이 충분하지 않을 때, 잘 지어진 이름으로 static 팩토리 메소드를 사용해 더 쉽게 사용하고 가독성이 좋은 코드를 만들 수 있다.
예시로 BigInteger(int, int, Random)
이 static 팩토리 메소드를 사용하고있다.
추가로 생성자는 시그니처에 제약이 있다. 똑같은 자료형의 파라미터로 받는 생성자를 중복되도록 만들 수 없다. 이 경우에도 static 팩토리 메소드를 사용한다.
예제public class Foo {
String name;
String address;
public Foo() {}
public Foo(String name) {
this.name = name;
}
public Foo(String address) {
this.address = address;
}
// 불가능
// public Foo(String address) {
// this.address = address;
// }
// example: public static factory method
public static Foo writeName(String name) {
return new Foo(name);
}
public static Foo writeName(String name) {
return new Foo(name);
}
}
불변(Immutable) 클래스나 매번 새로운 객체를 만들 필요가 없는 경우 미리 만들어둔 인스턴스 또는 캐시해둔 인스턴스를 반환해줄 수 있다. Boolean.valueOf(boolean)
메소드가 그 경우에 해당된다. (Flyweight Pattern에 기반함 - 어떤 클래스의 인스턴스 한 개만 가지고 여러 개의 "가상 인스턴스"를 제공하고 싶을 때 사용하는 패턴)
public class Foo {
private static final IMFOO = new Foo();
public static Foo getFoo() {
return IMFOO;
}
}
클래스에서 만들어줄 반환 객체를 선택하는데 매우 뛰어난 유연함을 준다. 리턴 타입의 하위 타입의 인스턴스를 만들어줄 수 있어서 리턴 타입은 인터페이스로 지정하고 그 인터페이스의 구현체는 API로 노출 시키지 않지만 그 구현체의 인스턴스를 만들어 줄 수 있다.이 기술은 인터페이스 기반 프레임워크에 적합하다. java.util.Collection
이 그 예에 해당한다.
java.util.Collection
은 45개에 달하는 인터페이스의 구현체 인스턴스를 제공하지만 그 구현체들은 모두 non-public이다. 즉 인터페이스 뒤에 숨겨져 있고 그럼으로서 public으로 제공해야 할 API를 줄였을 뿐만 아니라 개념적인 무게(conceptual weight)까지 줄일 수 있었다.
여기서 개념적 무게란, 프로그래머가 어떤 인터페이스가 제공하는 API를 사용할 때 알아야할 개념의 개수와 난이도를 말한다.
개발자는 반환된 객체가 그 인터페이스의 API에 의해 정확하게 구체화 되었다는 것을 알고 있으므로, 추가적인 구현 docs를 읽지 않아도 된다.
게다가, static 팩토리 메소드를 사용하려면 사용자가 인터페이스에서 반환 객체를 참조해야한다. 이것은 클래스 구현을 참조하는 것보다 실용적이다.
public class Foo {
// 인터페이스 선에서 해결 가능
public static Foo getGoo() {
return new Goo();
}
}
public class Goo extends Foo {
// 굳이 알아볼 필요 없음
public Goo() {
// ...
}
}
장점 3과 같은 이유로 객체의 타입은 다를 수 있다. EnumSet
클래스는 생성자 없이 public static 메소드, allof()
, of()
등을 제공한다. 그 안에서 리턴하는 객체의 타입은 enum 타입의 개수에 따라 RegularEnumSet
또는 JumboEnumSet
으로 달라진다.
그런 객체 타입은 노출하지 않고 숨겨져 있기 때문에 추후 JDK의 변화에 따라 새로운 타입을 만들거나 기존 타입을 없애도 문제가 되지 않는다.
static 팩토리 메소드는 서비스 프로바이더 프레임워크의 근본이다. 예시로 JDBC가 있다.
서비스 프로바이더 프레임워크는 서비스의 구현체를 대표하는 서비스 인터페이스와 구현체를 등록하는데 사용하는 프로바이더 등록 API 그리고 클라이언트가 해당 서비스의 인스턴스를 가져갈 때 사용하는 서비스 엑세스 API가 필수로 필요하다. 추가로, 서비스 인터페이스의 인스턴스를 제공하는 서비스 프로바이더 인터페이스(SPI)를 만들 수 있는데, 그게 없는 경우 리플렉션을 사용해 구현체를 만들어 준다.
JDBC의 경우 DriverManager.registerDriver()
가 프로바이더 등록 API, DriverManager.getConnection()
이 서비스 엑세스 API, 그리고 Driver
가 서비스 프로바이더 인터페이스 역할을 한다.
자바 6부터는 java.util.ServiceLoader
라는 일반적인 용도의 서비스 프로바이더를 제공하지만, JDBC
가 그 보다 이전에 만들어졌기 때문에 JDBC는 ServiceLoader를 사용하진 않는다.
Service Provider Pattern이란?(작성중)
예를들어, Collections 프레임워크에 있는 편의를 위한 구현체(java.util.Collections
)는 상속할 수 없다. 불행중 다행인게 이건 단점을 가장한 장점이다. 개발자들이 상속대신 composition을 사용하게 장려하고 불변 타입을 요하기 때문에 장점이라 할 수 있다.
static 팩토리 메소드들은 API docs에 명시되어 있지 않는다.(보통 constructor는 리스트가 모두 있다.)
- Effective Java 3rd Edition by Joshua Bloch
- 백기선님 유튜브 동영상 및 자료
https://www.youtube.com/watch?v=X7RXP6EI-5E&list=PLOFN6hDJLxo0MYZd1z6GCaRdFWX3kTb6A&index=2