본문 바로가기
이펙티브자바

[이펙티브 자바]아이템1 생성자 대신 정적 팩터리 메소드

by 순원이 2023. 7. 13.

정적 팩터리 메서드

객체 생성 역할을 하는 클래스 메서드다.

장점

  1. 직관성
  • 이름으로 객체의 특성을 직관적으로 설명한다.
 public static Position createStartPosition() {
        return new Position(START_POSITION_VALUE);
    }
Position position = Position.createStartPosition();
  • 생성자는 똑같은 타입을 파라미터로 받는 생성자 두개를 만들 수 없는데, 정적 팩토리는 가능하다
public class Person {
    String name;
    String address;

    private Person() {
    }

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

    public static Person CreateByName(String name) {
        return new Person(name);
    }

    public static Person CreateByAddress(String address) {
        Person person = new Person();
    	person.address = address;
        return person;
    }
    ...
}
  1. 호출될 때마다 인스턴스를 새로 생성하지 않아도 됨(인트턴스 통제)
  • new 키워드를 사용하면, 객체는 새로 생성됨.
  • 자주 생성될 것 같은 인스턴스는 글래스 내부에 미리 생성해 놓은 다음 반환한다면 코드를 최적화할 수 있음
  • 생성자의 접근제어자를 private으로 설정하여 외부에서 new 키워드로 새로운 인스턴스를 생성할 수 없게 됨
  1. 반환 타입의 하위 타입 객체 반환 가능

클래스의 생성자는 해당 클래스의 인스턴스만 만들 수 있다. 하지만 정적 팩토리 메소드를 사용하면 하위 클래스의 인스턴스까지 반환할 수 있다. 새로운 인터페이스와 수많은 구현 클래스가 있을 때, 구현 클래스의 생성자로 인스턴스를 만드는게 아니라 인터페이스의 정적 팩토리 메소드로 인스턴스를 만들어서 개발자가 수많은 구현 클래스들을 이해하지 않고도 인터페이스를 사용할 수 있도록 할 수 있다.

// List 인터페이스로 구현 클래스의 인스턴스를 만들었다.
List<Integer> list = List.of(1, 2, 3, 4, 5);
  1. 인자에 따라 다른 클래스 객체 반환

Laptop 클래스에 가격에 따라 노트북 인스턴스를 다르게 반환하는 기능을 추가하고 싶다면 어떻게 하면 좋을까? 매개변수에 따라 다른 클래스의 인스턴스를 생성해서 반환해주면 될 것 이다. 이를 코드로 작성하면 아래와 같다.

class Laptop {
    static Laptop createByPrice(int price) {
        if (price < 500000) {
            return new LowQualityLaptop();
        }

        if (price < 1500000) {
            return new NormalLaptop();
        }

        return new HighEndLaptop();
    }

// ...}

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

인터페이스나 클래스가 만들어지는 시점에서 하위 타입의 클래스가 존재하지 않아도 나중에 만들 클래스가 기존의 인터페이스나 클래스를 상속 받으면 언제든지 의존성을 주입 받아서 사용가능하다. 반환값이 인터페이스가 되며 정적 팩터리 메서드이 변경없이 구현체를 바꿔 끼울 수 있다.

다른 누군가가 구현하고 있는 코드

public class ElectricCar extends Car {
    ...
}
public class Car {

  	public static Car getCar(int position) {
        Car car = new Car();

        // Car 구현체의 FQCN(Full Qualified Class Name)을 읽어온다
        // FQCN에 해당하는 인스턴스를 생성한다.
        // car 변수를 해당 인스턴스를 가리키도록 수정

        return car;
    }
}

getCar는 실행시점에 위 코드에 뭐가 적혀있냐에 따라 다르게 작동한다.

단점

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

정적 팩토리 메소드만을 사용하게 하려면 기존 생성자는 private으로 해야하고 상속을 할 수 없게된다. 하지만 이 단점은 상속보다 컴포지션 사용을 유도하고 불변 타입으로 만들려면 이 제약을 지켜야 한다는 점에서 장점으로 받아들일 수도 있다.

  1. 정적 팩토리 메소드는 프로그래머 찾기 어렵다.

생성자는 기본 문법이기 때문에 개발자가 새로운 공부없이 바로 사용할 수 있다. 하지만 정적 팩토리 메소드는 개발자가 해당 클래스에 정적 팩토리 메소드가 있다는 사실을 알아야 한다. 이 단점을 완화라기 위해서는 널리 쓰이는 이름을 사용하여 정적 팩토리 메소드를 만들어야 한다.

컨벤션

  • from : 하나의 매개 변수를 받아서 객체를 생성
    • ex. Date date = Date.from(instant);
  • of : 여러개의 매개 변수를 받아서 객체를 생성
  • valueOf: from과 of의 더 자세한 버전
    • BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
  • getInstance | instance : 인스턴스를 생성. 이전에 반환했던 것과 같을 수 있음.
  • newInstance | create : 새로운 인스턴스를 생성
  • get[OtherType] : 다른 타입의 인스턴스를 생성. 이전에 반환했던 것과 같을 수 있음.
  • new[OtherType] : 다른 타입의 새로운 인스턴스를 생성.

궁금한 점

  • 정적팩토리메소드를 쓰는 것이 나을까 builder를 쓰는 것이 나을까