티스토리 뷰

728x90
반응형

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


  • 클라이언트가 클래스의 인스턴스를 얻는 전통적인 수단은 public 생성자입니다. 하지만 전통적인 수단 이외에 클래스는 생성자와 별도로 정적 팩토리 메서드를 제공할 수 있습니다.
  • 위의 말은 크게 와닿지 않습니다. 조금 더 쉽게 표현을 해보자면 우리(?)는 지금까지 new 키워드를 사용하여 인스턴스를 생성하곤 했습니다. 이 방법 외에도 static 메서드를 사용하여 생성자를 생성하고 반환할 수 있습니다. 

 

그래서 static 메서드를 이용해서 생성자를 생성하고 반환하면 뭐가 좋을까?


💡이름을 가질 수 있습니다.

  • 아래 예제는 new 키워드를 사용하여 객체를 생성하고 있습니다. new 키워드를 사용하여 객체를 생성하고 있지만 어떤 용도로 객체 생성되어 사용되는지 코드로만 추측하기 어려운 부분이 있습니다.
  • 다음 예제에서는 정적 팩토리 메서드를 사용하여 객체를 생성하고 있습니다. static 메서드를 사용하여 객체를 생성 후 반환을 하고 있으며 메서드의 이름을 보고 객체의 생성 목적을 담아낼 수 있습니다.
// new 키워드를 사용하여 객체 생성
@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor
public class Member {

    private Long id;
    private String name;
    private int age;
}

public class EffectiveJavaApplication {

    public static void main(String[] args) {
        Member member = new Member(1L, "홍길동", 25);
    }
}

 

// 정적 팩토리 메서드를 사용하여 객체 생성
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {

    private Long id;
    private String name;
    private int age;

    public static Member createMember(Long id, String name, int age) {
        return Member.builder()
                .id(id)
                .name(name)
                .age(age)
                .build();
    }

    public static Member updateMember(Long id, String name) {
        return Member.builder()
                .id(id)
                .name(name)
                .build();
    }
}

public class EffectiveJavaApplication {

    public static void main(String[] args) {
        Member member = Member.createMember(1L, "홍길동", 25);
    }
}

 

💡호출될 때마다 인스턴스를 새로 생성하지 않아도 됩니다.

  • 싱글톤 패턴에 대하여
  • 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있습니다.

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

 

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

 

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

 

  • 위 3가지 조건에 해당하는 예제를 살펴보겠습니다.
  • SmartPhone 인터페이스를 사용하여 하위 타입의 객체를 생성할 수 있도록 하였습니다.
  • EffectiveJavaApplication의 main() 메서드를 보면 입력 매개변수에 따라 객체의 생성이 달라집니다.
  • SmartPhoneFactory 클래스를 사용하여 컴파일 시점이 아닌 런타임시에 객체를 생성하므로 작성하는 시점에 반환할 클래스가 존재하지 않아도 됩니다.
public interface SmartPhone {

    SmartPhone init(String name, int version);
}

@ToString
@AllArgsConstructor
public class SamsungSmartPhone implements SmartPhone {

    private String name;
    private int version;

    @Override
    public SmartPhone init(String name, int version) {
        return new SamsungSmartPhone(name, version);
    }
}

@ToString
@AllArgsConstructor
public class AppleSmartPhone implements SmartPhone {

    private String name;
    private int version;

    @Override
    public SmartPhone init(String name, int version) {
        return new AppleSmartPhone(name, version);
    }
}

public class SmartPhoneFactory {

    public static SmartPhone create(Type type) {
        SmartPhone smartPhone = null;

        if (type == Type.SAMSUNG) {
            smartPhone = new SamsungSmartPhone("갤럭시", 6);
        } else if (type == Type.APPLE) {
            smartPhone = new AppleSmartPhone("아이폰 미니", 12);
        }
        return smartPhone;
    }
}

public enum Type {

    SAMSUNG,
    APPLE
}

public class EffectiveJavaApplication {

    public static void main(String[] args) {
        SmartPhone samsungSmartPhone = SmartPhoneFactory.create(Type.SAMSUNG);
        SmartPhone appleSmartPhone = SmartPhoneFactory.create(Type.APPLE);

        System.out.println(samsungSmartPhone.toString()); // SamsungSmartPhone(name=갤럭시, version=6)
        System.out.println(appleSmartPhone.toString()); // AppleSmartPhone(name=아이폰 미니, version=12)
    }
}

 

그렇다면 단점은 뭘까?


  • 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩토리 메서드만 제공하면 하위 클래스를 만들 수 없습니다.
  • 정적 팩토리 메서드는 프로그래머가 찾기 어렵습니다.

 

정적 팩토리 메서드 명명 방식


💡 from - 매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드

Date date = Date.from(instant);

 

💡 of - 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드

Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);

 

💡valueOf - from, of보다 조금 더 자세하게 명명한 것일 뿐

BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);

 

💡instance, getInstance - (매개변수를 받는다면) 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지 않는다.

// StackWalker 클래스에 속해있는 객체들 중에서, 인수가 “apple”일 때 반환하는 객체를 반환할 것이다.
StackWalker luke = StackWalker.getInstance("apple");

 

💡create, newInstance - (매개변수를 받는다면) 매개변수로 명시된 인스턴스를 반환합니다. 이때 매번 새로운 인스턴스를 생성해 반환합니다.

 Object newArray = Array.newInstance(classObject, arrayLen);

 

💡getType - 생성할 클래스가 아닌 다른 클래스에 팩토리 메서드를 정의할 때 사용합니다. Type은 팩토리 메서드가 반환할 객체의 타입 이름을 통틀어서 일컫는 말입니다.

FileStore fs = Files.getFileStore(path);

 

💡newType = 생성할 클래스가 아닌 다른 클래스에 팩토리 메서드를 정의할 때 사용합니다. 이때 매번 새로운 인스턴스를 생성해 반환함을 보장합니다.

 BufferedReader br = Files.newBufferedReader(path);

 

💡결론

  • 되도록이면 생성자보다는 정적 팩토리 메서드를 사용하자.

 

 

참고자료)

이펙티브 자바

https://jaeseongdev.github.io/development/2021/01/05/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C_%EC%9E%90%EB%B0%94_%EC%95%84%EC%9D%B4%ED%85%9C_1/

 

 

 

728x90
반응형