클래스 타입의 인스턴스 생성 방식에는 new 연산자를 통한 생성자 호출 방법 대신 정적 팩터리 메서드를 제공할 수 있다.
정적 팩터리 메서드의 장점이 있지만 단점 또한 존재한다.
장점
1. 이름을 가질수 있다.
널리 사용되는 네이밍 컨벤션이 존재하며 자유로운 이름 지정을 통해 생성자에 비해 명확하다.
생성자의 경우 클래스와 동일한 이름과 매개변수의 타입과 개수의 차이를 두어 다양하게 제공 가능하나
각 생성자에 대한 설명 없이는 파악하는데 어려움이 있다.
public class Pros1 {
private String name;
// private 생성자
private Pros1(String name) {
this.name = name;
}
public static Pros1 of(String name) {
return new Pros1(name);
}
public String getName() {
return name;
}
}
public class Main {
public static void main(String[] args) {
// private을 통한 기본 생성 불가
// Pros1 pros1 = new Pros1("study");
Pros1 pros1 = Pros1.of("study");
System.out.print(pros1.getName());
}
}
2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.
생성된 인스턴스를 캐싱하고, 동일한 정보를 가진 인스턴스를 호출할 경우 캐싱된 인스턴스를 재활용할 수 있다.
인스턴스들의 생성을 통제 가능하며 이를 통해 동치인 인스턴스가 하나임을 보장할 수 있다.
public class Pros2 {
private static Pros2 pros2 = null;
private Pros2() {}
static Pros2 getInstance() {
if(pros2 == null) {
pros2 = new Pros2();
}
return pros2;
}
}
public class Main {
public static void main(String[] args) {
Pros2 instance1 = Pros2.getInstance();
Pros2 instance2 = Pros2.getInstance();
System.out.println("instance1 == instance2 >> " + (instance1 == instance2));
System.out.print("instance1.equals(instance2) >> " + instance1.equals(instance2));
}
}
인스턴스 통제는 플라이 웨이트 패턴의 뿌리가 된다.
생성 비용이 큰 객체가 자주 요청되는 상황에서 성능의 이점을 볼수 있다.
3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
인터페이스 기반으로 명시된대로 동작하는 객체를 얻을 수 있기에
각 구현 클래스들이 무엇이 있는지 찾아보지 않아도 된다.
이러한 유연성을 응용하여 API를 작게 유지할 수 있다.
4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
반환 타입이 하위 타입이면 어떠한 객체든 반환할 수 있다.
이점은 릴리즈를 예로 생각해볼 수 있는데, 다음 릴리즈를 통해 클래스의 생성 수정 삭제 등에 용이하다.
장점3, 4에 대한 통합 예제이다.
public interface Pencil {
//default Pencil of(String color) {
static Pencil of(String color) {
if("Red".equals(color)) {
return new RedPencil();
}else if ("Blue".equals(color)) {
return new BluePencil();
} else {
return new NotPencil();
}
}
void info();
}
public class RedPencil implements Pencil {
public void info() {
System.out.println("빨간색의 색연필 입니다.");
}
}
public class BluePencil implements Pencil {
public void info() {
System.out.println("파란색의 색연필 입니다.");
}
}
public class NotPencil implements Pencil{
public void info() {
System.out.println("지원하지 않는 색상입니다.");
}
}
public class Main {
public static void main(String[] args) {
Pencil.of("Red").info();
Pencil.of("Blue").info();
Pencil.of("Black").info();
}
}
5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
인터페이스나 클래스를 상속 받는 상황이라면 언제든지 의존성을 주입 받아서 사용이 가능하다.
JDBC를 생각해보면, java 진영에서 제공하는 db 프로그래밍 API이다.
이를 예로 서비스 제공자 프레임워크를 살펴보겠다.
서비스 제공자 프레임워크 : JDBC
- 서비스 인터페이스 : 인터페이스에 맞춰 동작을 구현을 정의 한다.
각 DB를 운영하는 회사는 이 인터페이스를 구현하여 JDBC에 등록한다.
- 제공자 등록 API : 알맞는 JDBC를 반환 받기 위해 메타데이터를 등록한다.
- 서비스 접근 API : 서비스 제공자의 구현체를 얻을 수 있게 해주는 API
서비스 제공자 인터페이스 : 서비스 제공자 구현체이며, 제공하지 않을 경우 리플렉션을 통한 인스턴스 생성이 필요하다.
서비스 제공자 프레임워크 패턴의 변형으로 더 풍부한 서비스 인터페이스를 반환하기 위해 브릿지 패턴이 존재한다.
단점
1. 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.
public class Name {
private String name;
// private 생성자
private Name(String name) {
this.name = name;
}
public static Name of(String name) {
return new Name(name);
}
public String getName() {
return name;
}
}
// public class Cons1 extends Name{ --> private 생성자로 인해 불가
public class Cons1 {
private Name name = Name.nameOf("study");
}
2. 정적 팩터리 메서드는 프로그래머가 찾기 어렵다.
API document를 만들어보면 생성자 처럼 구분 없이 정적메서드로서만 표현이 되어 부가 설명이 없게되면
클래스 인스턴스 방식을 한번에 알아보기 어려울 수 있다.
아래는 예제를 Java Document로 변환한 사진이다. 메서드에 대한 설명이 없으면 생성자인지 판단하기 어렵다.
예제 코드
'Effective Java > 정리' 카테고리의 다른 글
[Item 8] finalizer와 cleaner 사용을 피하라 (0) | 2023.01.17 |
---|---|
[Item 7] 다 쓴 객체 참조를 해제하라 (0) | 2023.01.17 |
[Item 6] 불필요한 객체 생성을 피하라 (0) | 2023.01.17 |
[Item 3] private 생성자나 열거 타입으로 싱글턴임을 보증하라 (0) | 2023.01.07 |
[Item 2] 생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2023.01.06 |