[이펙티브 자바] item 1. 생성자 대신 정적 팩터리 메서드를 고려하라.

ideal dev·2023년 7월 6일
0

이펙티브 자바

목록 보기
1/3

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

반드시X ! 고려하라 !

먼저 생성자란?

  • 자바에서는, 객체의 생성과 동시에 인스턴스 변수를 원하는 값으로 초기화할 수 있는 생성자(constructor)라는 메소드를 제공
  • 클래스 이름과 똑같은 메소드가 있다? = 생성자
  • 자바의 모든 클래스에는 하나 이상의 생성자가 정의되어 있어야 합니다.
    • 자바에서 생성자는 필수인데, 작성안하고 넘어가는 경우도 많다. 이럴 땐 자바 컴파일러에서 자동으로 기본 생성자(default constructor)를 제공해 괜찮다 ~!
    • 기본 생성자 : 매개변수를 하나도 가지지 않으며, 아무런 명령어도 포함하고 있지 않음
      ex) Order(){}
public class Order {
    private boolean prime ;
    private boolean urgent ;
    private Product product ;

    public Order(Product product, boolean prime){ // 생성자
        this.product = product;
        this.prime = prime;
    }
}

정적 팩터리 메서드란?

  • public static

장점 5가지

장점1. 이름을 가질 수 있다. (동일한 시그니처의 생성자를 두개 가질 수 없다.)

언제 사용하냐?
Order(주문)을 관리하는데, 긴급주문도 생성자에 추가하고 싶다면?

public class Order {
    private boolean prime ;
    private boolean urgent ;
    private Product product ;

    public Order(Product product, boolean prime){ // 기본 주문
        this.product = product;
        this.prime = prime;
    }

    public Order(boolean urgent, Product product){ // 긴급 주문 추가
        this.product = product;
        this.urgent = urgent;
    }
}

하지만 생성자에서 똑같은 이름의 파라미터 순서만 변경하는 것은, 혼란을 야기할 가능성이 있다.

여기서 정적 팩토리 메서드 패턴을 사용한다면, 메소드명을 통해 각각의 역할구분이 가능해진다.
( 생성자의 시그니처가 중복되는 경우 사용한 사례 )

public class Order {
    private boolean prime ;
    private boolean urgent ;
    private Product product ;

    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 ;
    }
}

장점2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다. (Boolean.valueOf)

  • 생성자는 호출될 때 마다 새로운 인스턴스를 생성함.
public class Settings {

   private boolean useAutoSteering;
   private boolean userABS;
   private Settings() {} 

    public static void main(String[] args){
        System.out.println(new Settings());
        System.out.println(new Settings());
        System.out.println(new Settings());
    }
}

위 코드 실행 시, 아래와 같이 인스턴스를 각자 생성.

정적 팩토리 메소드는 !?
-> 객체의 생성은 내가 담당하겠어 ! 란 의미

public class Settings {

    private boolean useAutoSteering;
    private boolean userABS;
    
    private Settings() {} 

    private static final Settings SETTINGS = new Settings();
    
    public static Settings newInstance() { // 생성해둔 객체를 전달
        return SETTINGS;
    }
    
    public static void main(String[] args){
        Settings settings1 = Settings.newInstance();
        Settings settings2 = Settings.newInstance();

        System.out.println(settings1);
        System.out.println(settings2);
    }
}

위 코드 실행 시, 똑같은 인스턴스 사용

장점3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다. (인터페이스 기반 프레임워크, 인터페이스에 정적 메소드)

  • 반환할 객체의 클래스를 자유롭게 선택할 수 있는 유연성을 제공
  • API를 만들 때 이를 응용하면 구현 클래스를 공개하지 않고도 그 객체를 반환할 수 있어 API를 작게 구현 할 수 있다
public interface HelloService {

  String hello();

  static String hi() {
      prepareMessage();
      return "hi";
  }

  static private void prepareMessage() {
  }

  static String hi1() {
      prepareMessage();
      return "hi";
  }

  static String hi2() {
      prepareMessage();
      return "hi";
  }

  default String bye() {
      return "bye";
  }
}
public class HelloServiceFactory {

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    ServiceLoader<HelloService> loader = ServiceLoader.load(HelloService.class);
    Optional<HelloService> helloServiceOptional = loader.findFirst();
    helloServiceOptional.ifPresent(h -> {
        System.out.println(h.hello());
    });

    HelloService helloService = new ChineseHelloService();
    System.out.println(helloService.hello());

//        Class<?> aClass = Class.forName("me.whiteship.hello.ChineseHelloService");
//        Constructor<?> constructor = aClass.getConstructor();
//        HelloService helloService = (HelloService) constructor.newInstance();
//        System.out.println(helloService.hello());
    }

}

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

  • 반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관 없다.

장점5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다. (서비스 제공자 프레임워크)

단점 2가지

단점1. 상속을 하려면 public이나 protected 생성하기 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.

  • 상속이 안됨 !!

단점2. 정적 팩터리 메서드는 프로그래머가 찾기 어렵다.

  • Javadoc 활용 !

정리

정적 팩터리 메서드와 public 생성자는 각자의 쓰임새가 있으니 상대적인 장단점을 이해하고 사용하는 것이 좋다. 그래도 정적 팩토리를 사용하는 게 유리한 경우가 더 많으므로 무작정 public 생성자를 쓰지는 말자.

  • 열거 타입은 인스턴스가 하나만 만들어짐을 보장한다
  • 같은 객체가 자주 요청되는 상황이라면 플라이웨이트 패턴을 사용할 수 있다
  • 자바 8부터는 인터페이스가 정적 메서드를 가질 수 없다는 제한이 풀렸기 때문에 인스턴스화 불가 동반 클래스를 둘 이유가 별로 없다.
  • 서비스 제공자 프레임워크를 만드는 근간이 된다.
  • 서비스 제공자 인터페이스가 없다면 각 구현체를 인스턴스로 만들 때 리플렉션을 사용해야 한다.

참고

이펙티브 자바 책, 인프런 백기선님 강의

0개의 댓글