[Effective Java] 생성자 대신 정적 팩토리 메소드를 고려하라

ITEM 1. 생성자 대신 정적 팩토리 메소드를 고려하라

💡 클래스의 인스턴스 얻기

(1) PUBLIC 생성자

public class Computer {
     private String os;

    public Computer(String os) {
        this.os = os;
    }
}
public class Main {
    public static void main(String[] args) {
        Computer appleCom = new Computer("mac");
        Computer windowCom = new Computer("window");
    }
}

(2) 정적 팩토리 메소드

public class Computer {
     private String os;

    private Computer(String os) {
        this.os = os;
    }

    public static Computer WindowComputer() {
        return new Computer("window");
    }
    
    public static Computer AppleComputer() {
        return new Computer("mac");
    }
}
public class Main {
    public static void main(String[] args) {
        Computer.AppleComputer();
        Computer.WindowComputer();
    }
}

🔎 정적 팩토리 메소드 ?

📍 static method ?

  • 클래스가 메모리에 올라갈 때 자동적으로 생성되어 인스턴스를 생성하지 않아도 호출이 가능하다.

🏭 Factory ?

= 객체를 생성하는 역할을 하는 클래스 정적 메서드

.

🔎 정적 팩토리 메소드의 장점

(1) 이름을 가질 수 있다.

// public 생성자
Computer(String, int, int)
// 정적 팩토리 메소드
Computer.appleComputer()
  • public 생성자와 정적 팩토리 메소드중 어떤 것이 더 의미 설명이 잘 되어있는지 보면 정적 팩토리 메소드임을 알 수 있다. (애플컴퓨터? os는 mac이겠구나)
  • public 생성자를 사용한다면 개발자는 매개변수의 순서와 어떤 역할을 하는지 잘 알고 있어야한다.
  • 생성자는 메소드 시그니처의 제약이 있다.

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

public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE;
}
public class Computer {
    private static Map<String, Computer> computerMap = new HashMap<>();
    private String OS;
    public Computer(String OS) {
        this.OS = OS;
    }
    static {
        computerMap.put("APPLE", new Computer("MAC"));
        computerMap.put("MICROSOFT", new Computer("WINDOW"));
        computerMap.put("SAMSUNG", new Computer("TIZEN"));
    }

    public static Computer valueOf(String name) {
        if(computerMap.containsKey(name)) {
            return computerMap.get(name);
        }
        return new Computer(name);
    }
}
  • 불변 클래스는 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다.
  • 위의 Boolean class를 보면 상수 객체로 선언을 해줬기 때문에 매번 새로운 인스턴스를 생성하지 않는걸 볼 수 있다.
    => 생성비용이 큰 객체가 자주 요청되는 상황이라면 성능을 상당히 끌어올려준다.
  • 위의 Computer class를 보면 객체를 캐싱해서 쓰고있어 일일히 new 연산을 사용할 필요없이 valueOf 메서드를 활용하여 캐싱해둔 객체를 반환할 수 있다.

(2-1) 인스턴스 통제 클래스 : 인스턴스를 살아 있게 할지 철저히 통제할 수 있다.

  • 인스턴스를 통제하면 클래스를 싱글톤으로 만들 수 있다. => #싱글톤 : 하나의 인스턴스만 생성해서 사용
class Singleton {
    private Singleton(){};
    private static final Singleton INSTANCE = new Singleton();
    public Singleton getInstance() {
        return INSTANCE;
    }
}
  • 인스턴스를 통제하면 인스턴스화 불가로 만들 수 있다. => #인스턴스화 : 클래스로부터 객체를 만든다. (new)
class Instantiae {
    private Instantiae(){};
    public Singleton getInstance() {
        return new Instantiae();
    }
}
  • 불변 값 클래스에서 동치인 인스턴스가 단 하나뿐임을 보장할 수 있다. => 동치 : a.equals(b) = true;
public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE;
}
  • 열거 타입은 인스턴스가 하나만 만들어짐을 보장한다.=> 열거 타입은 상수이기 때문
public enum Enumeration {
    FIRST, SECOND, THIRD ;
}

class Main {
    public static void main(String[] args) {
        Enumeration test1 = Enumeration.FIRST;
        Enumeration test2 = Enumeration.SECOND;
        Enumeration test3 = Enumeration.FIRST;
        System.out.println(test1 == test2); // false
        System.out.println(test1 == test3); // true 
    }
}

(3) 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.

출처 : https://www.simplilearn.com/tutorials/java-tutorial/java-collection
public class Computer implements ComputerInterface{}

public interface ComputerInterface {
	public static Computer getComputer() {
    	return new Computer();
    }
}
  • 반환할 객체의 클래스를 자유롭게 선택할 수 있게하는 엄청난 유연성
  • API를 만들 때 유연성을 응용하면 구현 클래스를 공개하지 않고도 그 객체를 반환할 수 있어 API를 작게 유지할 수 있다.
    => 인터페이스 기반 프레임워크를 만드는 핵심 기술
  • Collections는 인스턴스화 불가 클래스이며 구현체들은 해당인터페이스에서 정적 팩토리 메서드를 통해 얻음
  • Collection을 상속받는 모든 타입의 인스턴스가 리턴될 수 있다.

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

public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
    Enum<?>[] universe = getUniverse(elementType);
    if (universe == null)
        throw new ClassCastException(elementType + " not an enum");

    if (universe.length <= 64)
        return new RegularEnumSet<>(elementType, universe);
    else
        return new JumboEnumSet<>(elementType, universe);
}
  • EnumSet의 noneOf()
    -> universe.length <= 64 면 long변수 ReqularEnumSet 인스턴스 return
    -> universe.length > 64 면 long 배열로 관리하는 JumboEnumSet 인스턴스 return
  • 반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관없다.
  • 필요 없는 하위 클래스는 삭제해도 문제가 없다.

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

  • 서비스 인터페이스 : 구현체의 동작을 정의
  • 제공자 등록 API : 제공자가 구현체를 등록할 때 사용
  • 서비스 접근 API : 클라이언트가 서비스의 인스턴스를 얻을 때 사용
  • 서비스 제공자 인터페이스 : 서비스 인터페이스의 인스턴스를 제공
  • 클라이언트는 서비스 접근 API 를 사용할 때 원하는 구현체의 조건을 명시할 수 있다.

JDBC

<제공>

  • 서비스 제공자 인터페이스 : Driver
  • 제공자 등록 API : DriverManager.registerDriver()
    => 정적 팩토리 메서드
    => Driver가 로드되는 static에 호출, 제공자(DB)에 맞는 Connection 반환

<서비스>

  • 서비스 인터페이스 : Connection
  • 서비스 접근 API : Driver.Manager.getConnection()
    => Connection 인터페이스 return
    => 호출 되기 전, 미리 등록된 제공자가 있다고 확신하고 그에맞는 Connection return

🔎 정적 팩토리 메서드 단점

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

(2) 정적 팩토리 메서드는 프로그래머가 찾기 어렵다.

  • 생성자처럼 API 설명에 명확히 드러나지 않음

좋은 웹페이지 즐겨찾기