본문 바로가기

java

[Effective Java] 생성자 대신 static 팩토리(factory) 메소드 사용을 고려하자


Effective Java 이펙티브 자바Effective Java를 구매하여 매일 한 항목씩 읽으리라 다짐하며 공부하기 시작하였다. 

항목은 총 78항목(2판 기준)으로 큰 챕터는 아래와 같다.


1. 개요

2. 객체의 생성과 소멸

3. 모든 객체에 공통적인 메소드

4. 클래스와 인터페이스

5. 제네릭

6. 열거형과 주석

7. 메소드

8. 프로그래밍 일반

9. 예외

10. 동시성

11. 직렬화


이번에 정리하고자하는 내용은 챕터 2의 1항목인 "생성자 대신 static 팩토리(factory) 메소드 사용을 고려하자"이다.

블로그 작성 목적은 복습과 이 책을 다 읽고자하는 다짐으로 시작한다.


생성자

객체 기반의 프로그래밍을 하다보면 객체를 생성하여야 한다. 객체를 생성한다를 다른 표현으로 인스턴스를 생성한다라고 하며 해당 클래스의 객체를 생성하기 위해서는 public 생성자가 제공되어야 한다.

아래는 일반적인 객체 생성의 예이다.


Provider p = new Provider();


위와 같이 객체를 생성하기 위해서는 Provider 클래스에는 public 생성자가 필요하다는 것이다. 

이정도는 무수한 자바 기본서에 나오는 내용이고 이 책의 해당 항목은 생성자 대신 static 팩토리 메소드를 사용을 하란다.

그렇다면 static 팩토리 메소드는 멀까? 간단하다.


static 팩토리 메소드

아래는 책에서 나오는 Boolean 클래스의 예제이다.


public static Boolean valueOf(boolean b){
     return b ? Boolean.TRUE : Boolean.FALSE;
}

그냥 별거 없다... static 메소드이다. 


그렇다면 왜?? 생성자 대신에 쓰는게 좋을까??

장점1 생성자와 달리 자기 나름의 이름을 가질 수 있다.

   그렇다. 생성자는 클래스 이름과 동일해야 한다. static 팩토리 메소드를 사용하면 얼마든지 매개변수와 반환 객체를 

   잘 표현할 수 있는 메소드 네이밍이 가능하다.

   예를 들어 BigInteger.probablePrime 메소드가 있다.

   요점은 클래스에서 동일한 시그니처를 갖는 여러 개의 생성자가 필요한 경우에는 생성자 대신 static 팩토리 메소드를 

   사용하되, 메소드 간의 차이점을 부각시키도록 신중하게 이름을 선정하라는 거다.


장점2 생성자와 달리 호출될 때마다 매번 새로운 객체를 생성할 필요가 없다.

   이미 생성된 인스턴스를 다시 사용할 수 있으며, 불필요하게 중복된 인스턴스들이 생성되는 것을 방지한다.

   여기서 드는 생각은 싱글톤이다. 

   static 팩토리 메소드는 여러번 호출되더라도 이미 생성된 동일 객체를 반환할 수 있으며 이 기법은 Flyweight 패턴과 

   유사하다.


장점3 자신의 클래스 인스턴스만 반환하는 생성자와 달리 static 팩토리 메소드는 자신이 반환하는 타입의 어떤 서브타입    객체도 반환할 수 있다.

   읭??? 이건 또 무슨말이냐... 서브타입 객체?? 다형성에 관한 얘기인가???

   반환되는 객체의 클래스를 선택해야 할 때 유연하단다... 아직까지도 모르겠다.. 더 보자..

   자바 데이터베이스 연결 API로 이해할 수 있겠다.

   서비스 제공자(모듈)가 하나의 서비스를 구현하는 시스템으로써... 이말인 즉 슨 서비스란 인터페이스가 정의된 클래스라    생각할 수 있고 그 인터페이스에 따라 서비스 제공자는 서비스를 사용할 클라이언트를 위한 구현체(클래스 등)을 만든다.

   결국 인터페이스 다형성이라는 얘기네... 아 어려워... 나도 맞게 이해했나 모르겠다...

   결국 클라이언트는 내부에 감추어진 구현 클래스를 알 필요가 없으며 어떤 서브 클래스라는 것만 염두해 두면 된다.

   책의 예제기반으로 간략하게 코드로 표현하면(주요한 부분 이외는 많은 부분이 생략되어있다.)


//서비스 인터페이스
public interface Service{
}
//서비스 제공자 인터페이스
public interface Provider{
    Service newService();
}
public class Service {
    private Service() {} //인스턴스 생성 X

    public static Service newInstance(String name){
         //클라이언트에서 필요한 서비스 제공자에 맞는 제공자명을 넘겼을 경우 저장된 providers 저장공간에서 해당 할당받아 객체를 생성한다.
         Provider p = providers.get(name)
         //해당 제공자 객체에서 구현된 newService 메소드를 리턴

return p.newService(); } }

역시 소스를 보니 조금 이해가 된다...


장점4 매개변수화 타입의 인스턴스를 생성하는 코드를 간결하게 해준다.

  static 팩토리 메소드를 사용하면 컴파일러가 타입 매개변수를 해결해준다. 이를 타입 추론이라고 하며 아래 소스를 보면     이해가 쉽다.

  

Map<String, List<String>> m = new HashMap<String, List<String>>();

  

타이핑 힘들고 복잡하다.

 이때 HashMap에서 아래와 같은 static 팩토리 메소드가 제공된다고 가정하면 가정이다 가정 실제 존재하지는 않는다.

 

public static <K,V> HashMap<K,V> newInstance(){
    return new HashMap<K,V>();
}
Map<String, List<String>> m = new HashMap.newInstance();

타입 매개변수를 연달아 두번 주었지만 static 메소드 제공으로 한번으로 줄어듬을 확인할 수 있다.


그렇다면 단점은 어떤게 있을까?

단점1 인스턴스 생성을 위해 static 팩토리 메소드만 갖고 있으면서 public이나 protected 생성자가 없는 클래스의 

  경우 서브 클래스를 가질 수 없다. 이럴 경우 상속이 불가능하하며 컴포지션 패턴 사용.

단점2 다른 static 메소드와 쉽게 구별할 수 없다.


결론

대부분 습관화된 public 생성자를 사용하는 것 보단 static 팩토리 메소드를 사용하는게 좋을 때가 많으며 우선 고려하는 습관을 가지는게 좋다.