Java

[Effective Java] 2-1. 객체 생성과 파괴: 생성자 대신 정적 팩토리 메서드를 고려하라

noahkim_ 2024. 12. 26. 23:29

조슈아 블로크 님의 "Effective Java" 책을 정리한 포스팅 입니다.

 

1. 정적 팩토리 메서드

  • 클래스는 생성자와는 별도로 static factory method 제공이 가능함

 

2. 장점

이름을 가짐

반환될 객체의 특성이 묘사되도록 할 수 있음
public static Order primeOrder(Product product) {
    Order order = new Order();
    order.prime = true;
    order.product = product;
    return order;
}

public static Order urgentOrder(Product product) {
    Order order = new Order();
    order.urgent = true;
    order.product = product;
    return order;
}

 

한 클래스에 시그니처가 같은 생성자가 여러 개 필요할 경우 유용
public class Order {
    private Product product;
    private boolean prime, urgent;

    public Order(Product product, boolean urgent) {
        this.product = product;
        this.urgent = urgent;
    }

    public Order(Product product, boolean prime) {
        this.product = product;
        this.prime = prime;
    }
}
  • 동일 시그니처는 컴파일 에러가 발생함

 

호출될 때마다 인스턴스를 새로 생성하지 않아도 됨

캐시해둔 인스턴스를 재활용할 수 있음
public class Settings {
    private boolean useAutoSteering;
    private boolean useABS;
    private Difficulty difficulty;

    private Settings() {}

    private static Settings SETTINGS = new Settings();

    public static Settings newInstance() return SETTINGS;
}
  • 플라이웨이트 패턴과 매우 유사함
  • 불변 클래스를 다룰 때 유용
  • 매개 변수에 따라 다른 인스턴스 객체를 리턴할 수 있음 (ex. Boolean.valueOf(boolean))

 

인스턴스 생성을 철저히 통제할 수 있음
  • 싱글턴 클래스화
  • 인스턴스화 불가
  • 불변 클래스에서 동치인 인스턴스가 단 하나뿐임을 보장 (열거 타입)

 

반환 타입의 하위 타입 객체로 반환 가능

원하는 하위 타입 선택 가능
  • 인터페이스
    • 정적 메서드 사용 가능
    • 기본 메서드 사용 가능

 

입력 매개변수에 따라 매번 다른 클래스의 객체 반환 가능

  • 클라이언트는 어떤 서브클래스 객체를 반환받을지 생각할 필요 없음

 

정적 팩터리 메서드를 작성하는 시점에 반환할 객체의 클래스가 존재하지 않아도 됨

추상화 제공
public static void main(String[] args) {
	String driverName = "com.mysql.jdbc.Driver";
	String url = "jdbc:mysql://localhost:3306/test";
	String user = "root";
	String password = "soyeon";
    
	try {
            Class.forName(driverName);

            // 서비스 접근 API인 DriverManager.getConnection가 서비스 구현체(서비스 인터페이스)인 Connection 반환
            Connection connection = DriverManager.getConnection(url, user, password);
	} catch (ClassNotFoundException e) {
            e.printStackTrace();
	} catch (SQLException e) {
            e.printStackTrace();
	}
}
  • 반환값이 인터페이스여도 됨
  • 인풋에 따라 자식클래스의 인스턴스 반환
  • 클라이언트 코드와의 종속성을 낮추고 결합도를 느슨하게 함
  • 이러한 유연함은 서비스 제공자 프레임워크를 만드는 근간이 됨

 

인스턴스를 만드는 일을 서브클래스에게 맡김
abstract class Shape {
    protected DrawingAPI drawingAPI;

    protected Shape(DrawingAPI drawingAPI) { this.drawingAPI = drawingAPI; }
            
    public abstract void draw();
}

class CircleShape extends Shape {
    private double x, y, radius;

    public CircleShape(double x, double y, double radius, DrawingAPI drawingAPI) {
        super(drawingAPI);
        this.x = x;
        this.y = y;
        this.radius = radius;
    }

    public void draw() { drawingAPI.drawCircle(x, y, radius); }
}
  • 추상화된 부분과 구현 부분을 서로 다른 클래스 계층구조로 분리
  • 서로 영향을 주지 않으면서 독립적으로 확장하도록 하기 위함
  • 브릿지 패턴과 유사

 

3. 단점

정적 팩토리 메서드만으로는 하위 클래스 생성 불가

  • 상속을 하려면 public이나 protected 생성자 필요

 

프로그래머가 찾기 어려움

이름에 대한 규약을 따라 짓는 식으로 완화
Date d = Date.from(instant);
Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
StackWalker luke = StackWalker.getInstance(options);
Object new Array = Array.newInstance(classObject, arrayLen);
FileStore fs = Files.getFileStore(path);
BufferedReader br = Files.newBufferedReader(path);
List<Complaint> litany = Collections.list(legacyLitany);
  • from
    • 하나의 매개변수 받아, 해당 타입의 인스턴스 반환
  • of
    • 여러개의 매개면수를 받아, 적합한 타입의 인스턴스 반환
  • valueOf
    • from과 of의 더 자세한 버전
  • getInstance
    • 매개변수로 명시한 인스턴스 반환.
    • 동일 인스턴스가 아님을 보장
  • newInstance
    • 매번 새로운 인스턴스 반환을 보장
  • get"Type"
    • getInstance와 같음
    • 팩토리 클래스에서 해당 타입의 인스턴스를 반환할 때 사용
  • new"Type"
    • newInstance와 같음
    • 팩토리 클래스에서 해당 타입의 인스턴스를 반환할 때 사용