자바

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

Hyun-danpung2 2022. 8. 24. 18:32
728x90
반응형

장점

1. 이름을 가질 수 있다.

// Example
public class Person{
    private String job;

    // 생성자
    public Person(String job){
        this.job = job;
    }
    // 정적 팩터리 메서드
    static Person jobOf(String job){
        return new Person(job);
    }

}

public class Main{
    public static void main(String[] args){
        // 생성자 이용
        Person p1 = new Person("student");
        // 정적 팩터리 메서드 이용
        Person p2 = Person.jobOf("student");
    }

}

위의 예시에서 Person 인스턴스를 만들 때, 생성자를 이용하는 경우 student가 무엇을 의미하는 지 알기 어렵다. 그러나 정적 팩터리 메서드를 이용하면 직업이 student인 Person이라는 것을 바로 알 수 있다.

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

// Example
public class Person{
  private String job;

    private static final Person Student = new Person("student");

    private Person(String job){
    this.job = job;
  }

    static public Person getNewStudent(){
        return Student;
    }

}

public class Main {
    public static void main(String[] args){
        Person s1 = Person.getNewStudent();
        Person s2 = Person.getNewStudent();
        System.out.println(s1 == s2); // true
    }

}

위의 예시에서 s1과 s2는 같다. 즉, 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다.
반복되는 요청에 같은 객체를 반환하는 방식은 인스턴스를 철저히 통제할 수 있다.(이런 클래스를 인스턴스 통제 클래스라 함)
인스턴스 통제로 클래스를 싱글턴으로 만들 수 있으며 인스턴스화 불가로 만들 수도 있고, 불변 값 클래스에서 동치인 인스턴스가 단 하나뿐임을 보장할 수 있다.

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

public abstract class Person{
  abstract void getInfo();

  static public Person getNewInfo(String code){
    Person person = null;
    if(code.indexOf("2") == 1){
      person = new Job();
    } else {
      person = new Age();
    }
    return person;
  }

}

class Job extends Person {
    public void getInfo() {
        System.out.println("student");
    }
}

class Age extends Person {
    public void getInfo() {
        System.out.println(23);
    }
}

public class Main {
    public static void main(String[] args){
        String code1 = "[1]";
        String code2 = "[2]";

        Person s1 = Person.getNewInfo(code1);
        Person s2 = Person.getNewInfo(code2);

        s1.getInfo();
        s2.getInfo();

        // 출력
        // 23
        // student
    }
}

API를 만들 때, 이를 응용하면 구현 클래스를 공개하지 않고 API를 작게 유지할 수 있다.(이는 인터페이스 기반 프레임워크를 만드는 핵심 기술이기도 함)
예를 들어 자바의 컬렉션 프레임워크는 총 45개의 유틸리티 구현체를 단 하나의 인스턴스화 불가 클래스인 java.util.Collections에서 정적 팩터리 메서드를 통해 얻도록 함으로써 API의 외견을 훨씬 작게 만들었다. 또한, 이는 프로그래머가 API를 사용할 때 실제 구현 클래스가 무엇인지 알아보지 않아도 되도록 프로그래머의 부담도 덜어낸 것이다.

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

public abstract class Person{
  abstract void getInfo();

  static public Person getNewInfo(String code, String job){
    return new Job();
  }

  static public Person getNewInfo(String code){
    return new Age();
  }

}

class Job extends Person {
    public void getInfo() {
        System.out.println("student");
    }
}

class Age extends Person {
    public void getInfo() {
        System.out.println(23);
    }
}


public class Main {
    public static void main(String[] args){
        String code1 = "[1]";
        String code2 = "[2]";
        String job = "job";

        Person s1 = Person.getNewInfo(code1);
        Person s2 = Person.getNewInfo(code2, job);

        s1.getInfo();
        s2.getInfo();

        // 출력
        // 23
        // student
    }
}

장점 3과 비슷한 의미로, 같은 이름의 메서드를 매개변수의 개수에 따라 다른 하위타입 클래스를 리턴할 수 있다는 뜻이다. 클라이언트는 위의 예에서 Job, Age 라는 클래스의 존재를 몰라도 된다.

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

이는 서비스 제공자 프레임워크를 만드는 근간이 된다. (예를 들어 JDBC - Java Databases Connectivity)
서비스 제공자 프레임워크는 3개의 핵심 컴포넌트로 구성된다.

  • 서비스 인터페이스(구현체의 동작을 정의)
  • 제공자 등록 API(제공자가 구현체를 등록할 때 사용)
  • 서비스 접근 API(클라이언트가 서비스의 인스턴스를 얻을 때 사용)

서비스 접근 API를 사용할 때, 구현체의 조건을 명시하여 사용할 수 있으며, 조건을 명시하지 않으면 기본 구현체 또는 지원하는 구현체를 반환한다.

단점

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

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

728x90
반응형