클라이언트가 클래스의 인스턴스를 얻는 전통적인 수단은 public 생성자다. 하지만 모든 프로그래머가 꼭 알아둬야 할 기법이 하나 더 있다. 클래스는 생성자와 별도로 정적 팩터리 메서드(static factory
method)를 제공할 수 있다.
클래스는 클라이언트에 public 생성자 대신 (혹은 생성자와 함께) 정적 팩터리 메서드를 제공할 수 있다. 이 방식에는 장점과 단점이 모두 존재한다.
public class Client {
public static void main(String[] args) {
Person person = new Person("Hello", 24);
Person.fromNameAndAge("Hello", 24);
}
}
public class Person {
private static final Person COLT = new Person("Colt", 28);
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public static Person getColt() {
return COLT;
}
}
public class Client {
public static void main(String[] args) {
Person colt = Person.getColt(); // Person 객체 내부의 상수 객체인 COLT 반환
}
}
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public static Colt createColtWithNameAndAge(String name, int age) {
return new Colt(name, age);
}
}
public class Client {
public static void main(String[] args) {
Colt person1 = Person.createColtWithNameAndAge("Colt", 28);
Colt person2 = new Person("Colt", 28); // 컴파일 에러가 발생한다.
}
}
public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>
implements Cloneable, java.io.Serializable {
// ...
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
Enum<?>[] universe = getUniverse(elementType);
if (universe == null)
throw new ClassCastException(elementType + " not an enum");
if (universe.length <= 64) // 클라이언트는 RegularEnumSet이 반환되는지 JumboEnumSet이 반환되는지 몰라도 된다. 그냥 알아서 상황에 맞게 반환될 뿐이다.
return new RegularEnumSet<>(elementType, universe);
else
return new JumboEnumSet<>(elementType, universe);
}
}
서비스 제공자 인터페이스가 없다면 각 구현체를 인스턴스로 만들 때 리플렉션을 사용해야 한다.
ex. JDBC에서 각각의 컴포넌트 및 역할
- Connection -> 서비스 인터페이스 역할
- DriverManager.registerDriver -> 제공자 등록 API 역할
- DriverManager.getConnection -> 서비스 접근 API 역할
- Driver -> 서비스 제공자 인터페이스 역할
public class Client {
public static void main(String[] args) {
Class.forName("oracle.jdbc.driver.OracleDriver"); // Class.forName()만 호출하면 DriverManager에 인수로 명시해준 Driver가 등록된다.
Connection conn = null;
conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:ORA92", "scott", "tiger");
// Statement..
}
}
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
// ...
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
}
Service Provider Interface는 Driver, Connection 인터페이스와 실제 그 인터페이스를 구현하는 구현체 클래스가 완전히 분리 되어 제공된다는 것이 포인트이다.
인터페이스를 사용해 틀을 만들어 놓고, 그 틀에 맞춰서 각각의 서비스 제공자들이 자신의 서비스에 맞춰서 구현 클래스를 제공하도록 하는 것이다.- [Java] Class.forName(String className) 그리고 Service Provider Interface
정적 팩터리 메서드와 public 생성자는 각자의 쓰임새가 있으니 상대적인 장단점을 이해하고 사용하는 것이 좋다. 그렇다고 하더라도 정적 팩터리를 사용하는 게 유리한 경우가 더 많으므로 무작정 public 생성자를 제공하던 습관이 있다면 고치자.