본문 바로가기

프로그래밍/JAVA

[JAVA] Effective Java - 1. 정적 팩터리 메서드

반응형

public 생성자 보단 정적 메서드로 제공하는 것이 유리하다.

 

 

장점 1. 이름을 가질 수 있다.

SecurityContext securityContext = SecurityContextHolder.createEmptyContext();

여기서 빈 SecurityContext 객체를 생성한다는 것을 직관적으로 알 수 있다.

반환될 객체의 특성을 쉽게 묘사 가능

 

장점 2. 호출 될 때 마다 새로운 객체생성을 하지 않아도 된다.

class ColorCollect {

  private static final Map<String, Color> colorMap = new HashMap<>();

  public static Color getColor(String colorName) {
    Color color = colorMap.get(colorName);
    if (color == null) {
      Color newColor = new Color(colorName);
      colorMap.put(colorName, newColor);
      return newColor;
    }
    return color;
  }
}
Color red = ColorCollect.getColor("red");

 

객체를 생성하지않고, 캐싱된 객체를 재활용하게 할 수 있다.

불필요한 객체생성을 막고, 인스턴스를 통제할 수 있다.

* Flyweight 패턴 / Singleton 패턴

 

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

static <T> Set<T> synchronizedSet(Set<T> s, Object mutex) {
    return new SynchronizedSet<>(s, mutex);
}

ex) java.util.Colletions의 정적 팩터리 메소드

 

반환할 객체를 자유롭게 선택할 수 있는 유연성이 있다.

여기서 사용자는 반환 객체인 구현체 SynchronizedSet의 상세내용을 알지 못해도,

Set Collection의 사용방법, 즉 인터페이스만 알아도 무관하다. 

 

장점 4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.

interface Membership {
  static Membership createMembership(String grade) {
    if ("A".equals(grade)) {
      return new VipMembership();
    }
    if ("B".equals(grade)) {
      return new GeneralMembership();
    }
    throw new IllegalArgumentException("No Grade");
  }
  int discount(int price);
}

class VipMembership implements Membership {
  @Override
  public int discount(int price) {
    return price - 10;
  }
}

class GeneralMembership implements Membership {
  @Override
  public int discount(int price) {
    return price - 5;
  }
}
public class Main {
  public static void main(String[] args) {
    Membership membership = Membership.createMembership("A");
    System.out.println(membership.discount(100));
  }
}

장점 3번의 연장이라고 생각됨.

파라미터에 따라 필요한 구현체를 반환 할 수 있다.

마찬가지로 하위 객체를 반환할 수 있는 유연성으로 인하여 구현 클래스를 공개하지 않고도 그 객체를 반환할 수 있고

클라이언트는 구현 객체에 구애받지 않고 명시된 인터페이스로 이용 가능

 

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

// DriverManager.register(new Driver());
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(DB_URL, USERNAME, PASSWORD);

이런 유연함의 예로 서비스 제공자 프레임워크인 JDBC를 예시로 들고 있다.

 1. 구현체의 동작을 정리하는 서비스 인터페이스 -> Connection

 2. 제공자가 구현체를 등록할 떄 사용하는 제공자 등록 API

            ->  소스에선 Class.forName("com.mysql.jdbc.Driver") = DriverManager.registerDriver();

 3. 클라이언트가 서비스의 인스턴스를 얻을 때 사용하는 서비스 접근 API -> DriverManager.getConnection()

 4. 서비스 인터페이스의 인스턴스를 생성하는 팩터리 객체를 설명 -> Driver 인터페이스

 

여기서 정적 팩터리 메서드인 DriverManager.getConnection()는

작성 당시에 반환 객체의 클래스(Connection 구현체)를 알 필요가 없다.

 

단점 1. 정적 팩터리 메서드만 제공 시 상속 불가능

class AClass {
  private AClass() {}
  public static AClass createAClass() {
    return new AClass();
  }
}

class BClass extends AClass { // 컴파일 에러
}

 

단점 2. 생성자 보다 메서드를 찾기 힘들다

생성자 처럼 명확히 드러나지 않으니 인스턴스화 할 방법을 따로 찾아야한다.

흔히 사용되는 명명 방식이 존재

 

명명 방식

from : 매개변수를 하나 받아서 해당 타입의 인스턴스로 반환하는 형변환 메서드

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

valueOf : from과 of의 더 자세한 버전

instance / getInstance : 매개변수로 명시한 인스턴스 반환, 같은 인스턴스임을 보장하지는 않음

create / newInstance : 새로운 인스턴스를 생성해 반환함을 보장

getType : 생성 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 사용

newType : 생성 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 사용 (새로운 인스턴스 보장)

type : getType/newType의 간결한 버전

반응형