본문 바로가기

개발/Java

[Effective Java] 생성자 대신 Static Factory Method를 고려하라

반응형

Effective Java 3/E

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


정적 팩터리 메서드(Static Factory Method)는 클래스에 정적 메서드를 정의하고, 생성자 대신 객체를 생성할 수 있게 만드는 기법입니다. 간단하게 정리하면 Method 호출 방식으로 객체를 생성하는 것입니다.

대표적인 예로 Java의 Wrapper 클래스에서 사용되는 것을 확인할 수 있습니다. 그 중 하나인 Boolean은 다음과 같은 API를 제공합니다.

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

 

public static void main(String args[]) {
    Boolean bool1 = new Boolean(true); // new 연산자 사용
    Boolean bool2 = Boolean.valueOf(true); // 정적 팩토리 메서드 사용
}

 

정적 팩터리 메서드 장단점


# 장점

1) 이름을 가진 생성자

생성자는 기본적으로 이름을 가질 수 없습니다. new BigInteger(~) 과 BigInter.probablePrime(~) 둘 중 어떤 메서드가 더욱 '소수인 BigInteger를 생성' 하는 의미로 알 수 있나요? 정적 팩터리 메서드를 통해 생성자의 역할과 의도를 명확하게 전달할 수 있습니다.

예시로 ISBN 또는 제목을 인자로 전달받아 책 객체를 생성하는 클래스가 있다고 가정하겠습니다.

class Book {
    private String title;
    private long isbn;

    // ISBN을 인수로 받는 생성자
    Book(long isbn) {
        if (isbn == 9788966262281L) {
            this.isbn = isbn;
            this.title = "채식주의자";
        } else if (isbn == 9788998139766L) {
            this.isbn = isbn;
            this.title = "소년이온다";
        }
    }

    // 제목을 인수로 받는 생성자
    Book(String title) {
        if ("채식주의자".equals(title)) {
            this.title = title;
            this.isbn = 9788966262281L;
        } else if ("소년이온다".equals(title)) {
            this.title = title;
            this.isbn = 9788998139766L;
        }
    }
}
Book book1 = new Book(9788966262281L); // 채식주의자의 ISBN
Book book2 = new Book("소년이온다);

new 키워드를 통해 객체를 생성하는 코드를 봤을 때, 생성자 인자로 ISBN 또는 책 제목을 전달해야한다는 사실을 직관적으로 이해할 수 없습니다. 또한 코드 내부를 확인하기 전까지는 어떠한 인자를 필요로 하는지 파악하는 데도 쉽지 않습니다. 심지어 ISBN은 ISBN10과 ISBN13 체계로 나뉩니다. 예시는 ISBN13을 기준으로 작성했지만, ISBN10까지 지원하도록 클래스를 확장한다면, 객체를 생성할 때 더욱 혼란스러울 것입니다.

class Book {
    private String title;
    private long isbn;

    private Book(String title, long isbn) {
        this.title = title;
        this.isbn = isbn;
    }

    static Book createByIsbn(long isbn) {
        if (isbn == 9788966262281L) {
            return new Book("채식주의자", isbn);
        } else if (isbn == 9788998139766L) {
            return new Book("소년이온다", isbn);
        }
        throw new IllegalArgumentException("일치하는 책이 없습니다.");
    }

    static Book createByTitle(String title) {
        if ("채식주의자".equals(title)) {
            return new Book(title, 9788966262281L);
        } else if ("소년이온다".equals(title)) {
            return new Book(title, 9788998139766L);
        }
        throw new IllegalArgumentException("일치하는 책이 없습니다.");
    }
}

생성자에는 private 접근 제어자로 설정하고, 정적(Static)인 메서드를 통해 private 생성자에 접근합니다.

Book book1 = Book.createByIsbn(9788966262281L); // 채식주의자의 ISBN
Book book2 = Book.createByTitle("소년이온다");

정적 팩토리 메서드를 통한 객체 생성 예시입니다. 메소드명을 보고 어떤 역할을 수행하는지 직관적으로 바로 알 수 있습니다. 

2) 인스턴스 통제 클래스

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

정적 팩터리 메서드는 인스턴스 재사용을 통해 불필요한 메모리 할당을 줄입니다. 반면 new 키워드의 객체는 생성할 때마다 새로운 인스턴스를 갖습니다. 이는 서로 다른 주소의 객체가 매번 Heap 메모리에 쌓이게 되면서, 메모리 뿐만 아니라 Garbage Collection(가비지 컬렉션)의 부담도 발생합니다.

java.lang.Integer 클래스의 정적 팩터리 메서드인 valueOf를 보겠습니다. (아래 IntegerCache는 참고입니다.)

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

 

 

static class IntegerCache {
    static final int low = -128; // 캐시 범위의 하한
    static final int high; // 캐시 범위의 상한
    static final Integer[] cache; // 캐시 배열

	// Static Initialization Block
    static {
        high = 127; // 캐시 범위의 상한 설정
        cache = new Integer[high - low + 1]; // 캐시 배열 초기화
        for (int i = low; i <= high; i++) {
            cache[i + (-low)] = new Integer(i); // 캐시 배열에 Integer 객체 생성 및 저장
        }
    }
}

valueOf(int i)에서 인자로 받은 i가 캐싱된 숫자 범위 내에 있다면, 캐시 배열에서 초기에 생성했던 인스턴스를 반환합니다. 반대로 존재하지 않는다면, 새로운 인스턴스를 만들도록 구현되었습니다. 

정리하자면, 단순히 new 키워드를 사용할 때보다

# 단점

 
 

 

 

반응형