본문 바로가기

책, 강의 정리/이펙티브자바

아이템 [1] - 생성자 대신 정적 팩터리 메서드를 고려하라

인스턴스를 생성하는 방법은 (1) public 생성자 (2) 정적 팩터리 메서드가 있다.

여기서 정적 팩터리 메서드를 사용할 때 찾을 수 있는 장점은,

 

1. 이름을 가질 수 있다.

 

/*(1) valueOf 라는 메서드 명을 통해 반환되는 값을 유추할 수 있다.*/
Boolean boolean1 = Boolean.valueOf(true); 

/* (2) 생성자는 시그니처 중복이 불가하다. 
   	   한 클래스에 시그니처가 중복되는 생성자가 여러개 반복될 것 같으면 정적 펙터리 메서드 사용을 고려해보자*/
Boolean.logicalOr(boolean a, boolean b);
Boolean.logicalAnd(boolean a, boolean b);

 

 

2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다. - JVM 클래스로더의 "초기화" 부분에서 할당됨. 

 

Foo foo = new Foo().valueOf(); (x)
Boolean boolean1 = Boolean.valueOf(); (o)

 

관련되어 나온 개념은 

더보기

불변클래스(immutable Class)를 만들 수 있음

Boolean.valueOf() -> Boolean을 반환하지만 객체를 생성하지 않음.

플라이웨이트 패턴

데이터를 공유하여 메모리를 절약하는 패턴으로 공통으로 사용되는 객체는 한 번만 생성되고 공유를 통해 풀(Pool)에 의해 관리, 사용된다. 없으면 만들고 있으면 있는거 주는 패턴

 

3. 반환타입의 하위 타입 객체를 반환할 수 있다.

4. 매개변수에 따라 다른 클래스 객체를 반환할 수 있다.(하위 타입의 조건만 만족한다면.)

 

public class Ticket {

    public static Ticket getTicketByType(String type) {
        if (type.equals("vip")) {
            return VipTicket.INSTANCE;
        }
        if(type.equals("general")){
            return GeneralTicket.INSTANCE;
        }

        new IllegalArgumentException("잘못된 타입입니다!");
    }

}

 

 

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

- 반환값이 인터페이스여도 된다.

- 정적팩터리 메서드의 변경 없이 구현체를 바꿔끼울 수 있다 -> 유연한 시스템 

 

import java.util.ArrayList;
import java.util.List;

public class TicketStore {
  /** TicketSeller는 인터페이스이고 구현체가 없음에도 아래와 같은 메서드 작성이 가능하다.**/
    public static List<TicketSeller> getSellers(){
        return new ArrayList<>();
    }
}

 

 

 

ex) 서비스 제공자 프레임워크 (JDBC예시: https://devyongsik.tistory.com/294)

- 서비스 인터페이스: 구현체의 동작 정의.

- 제공자 등록 API: 제공자가 구현체 등록

- 서비스 접근 API: 클라이언트가 서비스의 인스턴스 얻을 때 -> 원하는 구현체의 조건을 입력하고 그에 따라 기본 구현체를 반환하거나 조건에 부합하는 구현체를 반환할 수 있음. 이것을 정적 팩터리로 작성할 수 있다.

- 서비스 제공자 인터페이스(SPI): 서비스 인터페이스 인스턴스를 생성하는 팩터리 객체 설명

"보통의 API들은 구현체의 Interface를 외부로 공개하여 구현체를 사용하는 주체가 자신의 환경에 맞게 사용한다. 반면에 SPI는 사용자가 구현해야 할 Interface를 정의한다. SPI 사용자(보통은 driver vendor)가 자신의 환경에 맞는 구현체를 직접 정의하여 제공하면 SPI를 제공해준 service에서는 제공 받은 구현체를 불러다 사용하는 형태로 동작한다."

https://itnext.io/java-service-provider-interface-understanding-it-via-code-30e1dd45a091

 

Java Service Provider Interface (SPI)— understanding it via code

Ever thought how that JPA implementation (EclipseLink or Hibernate) is picked up by the Java Persistence APIs. Or How does…

itnext.io

 

 

** 서비스 인터페이스 제공자가 없다면 각 구현체를 인스턴스로 만들 때 리플렉션을 사용해야한다(이해안됨) **

 

 

 

 

서비스 제공자 프레임워크 패턴의 변형)  브리지패턴(더보기), 의존 객체 주입 프레임워크, ServiceLoader(https://www.journaldev.com/31602/java-spi-service-provider-interface-and-serviceloader)

 

더보기

<브리지패턴>

기능 클래스 계층과(공통기능 + a 를 위한 상속) 구현 클래스 계층(인터페이스를 구현)을 연결하는 패턴

 

 

 

 

 

 

 

 

단점은,

 

 

1. 상속을 하려면 public이나 protected 생성자가 필요하기 때문에 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없음

- Collections 는 상속할 수 없음

- 상속보다 컴포지션을 유도하기 때문에 OCP 유도. 장점이 될 수도 있음.(https://gmlwjd9405.github.io/2018/08/10/composite-pattern.html)

 

2. 찾기가 어려움. 통용되는 네이밍 준수 필요

- from: 매개변수를 받아서 해당 타입의 인스턴스 반환. 형변환.

- of: 여러 매개변수를 받아 적합한 인스턴스 반환

- valueOf: from, of 보다 자세한 버전

- instance, getInstance: 매개변수 인스턴스를 반환하지만 보장하지는 않음

- create, newInstance: 매번 새로운 인스턴스 생성해 반환

- getType: 반환 타입과 팩터리메서드 클래스가 다름. Type은 반환 타입 명시

                  FileStore fs = Files.getFileStore(path)

- type: getType, newType 간결한 버전

                  Collections.list(legacyLitany)

  •