2장 객체 생성과 파괴

이연희·2022년 8월 28일
0

이펙티브 자바

목록 보기
1/1

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

클라이언트가 클래스의 인스턴스를 얻는 전통적인 수단은 public 생성자다. 하지만 또 다른 기법으로 클래스가 생성자와 별도로 정적 팩터리 메서드(static factory method)를 제공하는 방법이 있다. 클래스의 인스턴스를 반환하는 단순한 정적 메서드이다.

장점
1. 이름을 가질 수 있다.
2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.
3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.

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

정적 팩터리 메서드와 public 생성자는 각자의 쓰임새가 있으니 상대적인 장단점을 이해하고 사용하는 것이 좋다. 그렇다고 해도 정적 팩터리를 사용하는 게 유리한 경우가 많으니 무작정 public 생성자를 제공하던 습관이 있으면 고치자.

2. 생성자에 매개변수가 많다면 빌더를 고려하라

정적 팩터리와 생성자는 선택적 매개변수가 많을 때 적절히 대응하기 어렵다는 점이 있다.

점층적 생성자 패턴(telescoping constructor pattern)을 사용할 수도 있지만, 매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다.

또 다른 방식으로 생성자를 객체로 만든 후 세터(setter) 메서드를 호출해 매개변수를 설정하는 자바빈즈 패턴(JavaBeans pattern)이 있다. 하지만 이 패턴은 객체 하나를 만드려면 메서드를 여러 개 호출해야 하고, 객체가 완전히 생성되기 전까지 일관성(consistency)가 무너진 상태에 놓인다. 따라서 클래스를 불변으로 만들 수 없다.

빌더 패턴(Builder Pattern)

점층적 생성자 패턴의 안정성과 자바빈즈 패턴의 가독성을 겸비한 빌더 패턴(Builder pattern)이 있다.

동작과정
1) 클라이언트는 필요한 객체를 직접 만드는 대신, 필수 매개변수만으로 생성자(혹은 정적 팩터리)를 호출해 빌더 객체를 얻는다.
2) 빌더 객체가 제공하는 일종의 세터 메서드들로 원하는 선택 매개변수들을 설정한다.
3) 매개변수가 없는 build 메서드를 호출해 드디어 우리에게 필요한 (보통은 불변인) 객체를 얻는다.

public class NutrutuibFacts{
	private final int servingSize;//필수
    private final int servings;//필수
    private final int calories;
    private final int fat;
    
    public static class Builder{
    	//필수 매개변수
        private final int servingSize;
        private final int servings;
        
        //선택 - 기본값으로 초기화
        private int calories=0;
        private int fat=0;
        
        public Builder(int servingSize, int servings){
        	this.servingSize=servingSize;
            this.servings=servings;
        }
        
        public Builder calories(int val){
        	calories=val;
            return this;
        }
        public Builder fat(int val){
        	fat=val;
            return this;
        }
        
        public NutritionFacts build(){
        	return new NutritionFacts(this);
        }
    }
    
    private NutritionFacts(Builder builder){
    	servingSize=builder.servingSize;
        servings=builder.servings;
        calories=builder.calories;
        fat=builder.fat;
    }
}

NutritionFacts 클래스는 불변이며 모든 매개변수의 기본값을 한곳에 모아 두었다. 빌더의 세터 메서드들은 빌더 자신을 반환하기 때문에 연쇄적으로 호출할 수 있다. 이런 방식을 플루언트 API(fluent API) 또는 메서드 연쇄(method chaining)라 한다.

다음은 클라이언트 코드의 모습이다.

NutritionFacts cocaCola = new NutritionFacts.Builder(240,8)
								.calories(100)
                           		.fat(2)
                           		.build();

빌더 패턴은 (파이썬과 스칼라에 있는) 명명된 선택적 매개변수(named optional parameters)를 흉내 낸 것이다.

빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 좋다.

생성자나 정적 팩터리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는 게 더 낫다. 매개변수 중 다수가 필수가 아니거나 같은 타입이면 특히 더 그렇다. 빌더는 점층적 생성자보다 클라이언트 코드를 읽고 쓰기가 훨씬 간결하고, 자바빈즈보다 훨씬 안전하다.

3. private 생성자나 열거 타입으로 싱글톤임을 보증하라

싱글톤(singleton)이란 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말한다. 클래스를 싱글톤으로 만들면 이를 사용하는 클라이언트를 테스트하기가 어려워질 수 있다.

타입을 인터페이스로 정의한 다음 그 인터페이스를 구현해서 만든 싱글톤이 아니라면 싱글톤 인스턴스를 가짜(mock) 구현으로 대체할 수 없기 때문이다.

싱글톤 생성 방식1 - public static final 필드 방식

public class Elvis{
	public static final Elvis INSTANCE = new Elvis;
    private Elvis(){
    	...
    }
}

private 생성자는 public static final 필드인 Elvis.INSTANCE를 초기화할 때 딱 한번만 호출된다. public이나 protected 생성자가 없으므로 Elvis 클래스가 초기화될 때 만들어진 인스턴스가 전체 시스템에서 하나뿐임을 보장한다.

싱글톤 생성 방식2 - 정적 팩터리 방식

public class Elvis{
	private static final Elvis INSTANCE = new Elvis;
    private Elvis(){
    	...
    }
    public static Elvis getInstance(){
    	return INSTANCE;
    }
}	

Elvis.getInstance는 항상 같은 객체의 참조를 반환하므로 두번째 인스턴스는 결코 만들어지지 않는다. public static 필드가 final이니 절대로 다른 객체를 참조할 수 없다.

정적 팩터리 방식의 첫번째 장점은 API를 바꾸지 않고도 싱글톤이 아니게 변경할 수 있다는 것이다. 스레드별로 다른 인스턴스를 넘겨주게 할 수 있다. 두번째 장점은 정적 팩터리를 제네릭 싱글턴 팩터리로 만들 수 있다는 점이다. Elvis::getInstance를 Supplier<Elvis>로 사용하는 식이다.

싱글톤 생성 방식3 - 열거 타입 방식

public enum Elvis{
	INSTANCE;
    public void leaveTheBuilding(){...}
}

public 필드 방식과 비슷하지만 더 간결하고 추가 노력없이 직렬화할 수 있다. 대부분 상황에서는 원소가 하나뿐인 열거 타입이 싱글톤을 만드는 가장 좋은 방법이다. 단 만들려는 싱글톤이 Enum 외의 클래스를 상속해야 한다면 이 방법은 사용할 수 없다. 열거 타입이 다른 인터페이스를 구현하도록 선언할 수는 있다.

4. 인스턴스화를 막으려거든 private 생성자를 사용하라

정적 메서드와 정적 필드만을 담은 클래스를 만들고 싶을 때가 있다. java.lang.Math와 java.util.Arrays처럼 기본 타입 값이나 배열 관련 메서드들을 모아놓을 수 있다. 또한 java.util.Collections처럼 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드(혹은 팩터리)를 모아놓을 수도 있다. 마지막으로 final 클래스와 관련한 메서드들을 모아놓을 때도 사용한다. final 클래스를 상속해서 하위 클래스에 메서드를 넣는 건 불가능하기 때문이다.

추상 클래스로 만드는 것으로는 인스턴스화를 막을 수 없다. 컴파일러가 기본 생성자를 만드는 경우는 오직 명시된 생성자가 없을 때뿐이니 private 생성자를 추가하면 클래스의 인스턴스화를 막을 수 있다.

public class UtilityClass{
	private UtilityClass(){
    	throw new AssertionError();
    }
    ...
}	

명시적 생성자가 private이니 클래스 바깥에서는 접근할 수 없다. 이 코드는 어떤 환경에서도 클래스가 인스턴스화되는 것은 막아준다. 이 방식은 상속을 불가능하게 하는 효과도 있다. 모든 생성자는 명시적이든 묵시적이든 상위 클래스의 생성자를 호출하게 되는데, 이를 private으로 선언했으니 하위 클래스가 상위 클래스의 생성자에 접근할 길이 막혀버린다.

5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라

profile
공부기록

0개의 댓글