이펙티브 자바 2장. 객체 생성과 파괴 아이템 2 : 생성자에 매개변수가 많다면 빌더를 고려하라.
결론 : "인스턴스를 한번에 생성하기 때문에 유효성 검사도 가능하고 객체의 불변성을 준다면 스레드 안정성을 가져오며 또한 세터 개념을 사용하기에 가독성도 좋다. 따라서 매개변수가 많고 복잡하여 생성자 생성이 복잡하다면 빌더를 고려하자"
점층적 생성자 패턴의 문제
- 점층적 생성자 패턴이란 여러개의 필드 중에 필수필드를 기준으로 설정을 해두고 선택필드에 대해서 점진적으로 늘려 생성자를 만드는 방식이다.
- 예를 들어, 총 20개의 필드가 있고 이 중에 필수 필드가 10개라면 나머지 10개의 필드에 대해서 1개를 받는 생성자 10개, 즉, 모든 경우의 수를 고려해 생성자를 만드는 경우이다.
- 이는 수많은 생성자를 생성해야하는 문제를 낳는다.
- "매개변수가 많아질수록 클라이언트 코드를 작성하거나 읽기 어렵다."
- 값의 의미, 매개변수의 수, 매개변수의 타입 등 고려해야할 사항들이 너무 많아진다.
- 이 클래스를 사용하는 클라이언트는 너무 복잡한 클래스를 사용하게 되는 것을 의미한다.
자바 빈즈 패턴의 문제
- 자바 빈즈 패턴이란 매개변수가 없는 생성자로 인스턴스를 만들고 세터를 이용해 원하는 객체의 필드 값을 설정하는 방식이다.
- 객체를 하나 만들기 위해서 여러개의 메서드를 호출해야 하며, 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓인다.
- 일관성이 무너진다는 의미는 예를 들어, 음식 정보 클래스가 있다고 가정했을 때, 칼로리가 1000 이상이라면 당은 50이하여야 한다는 내용이 있다고 가정한다면, 점층적 생성자 패턴에서는 이를 생성자에서 매개변수의 유효성 검사가 가능했는데 세터로 분리시 이러한 유효성 검사를 한번에 하는것이 불가능하게 된다. 이는 클래스를 불변으로 만들 수도 없으며, 스레드 안정성을 얻기도 어렵다.
코드를 통한 예시
- 아래와 같이 코드가 있다고 가정할 때, 여러 스레드가 해당 메서드에 해당 객체를 가져가는 요청을 한다고 가정하자.
- 이 때, 스레드마다 같은 값을 가져간다는 보장이 없다. 따라서 스레드 안정성에 문제가 생기고 일관성이 없다.
(위의 예시의 원인은 클라이언트가 해당 객체를 참조하는 순간에 따라 값이 다르기 때문이다.)
- 또한 언제든지 setter를 통해 클래스의 필드를 변경가능하게 하므로 불변 클래스를 만들 수 없게 되는 것이다.
(이는 freezing을 통해 해결가능하지만 잘 사용하지 않는다.)
Member member = new Member();
member.setName("james");
member.setAge(12);
불변 클래스
- 자바에서 불변 클래스란 한번 생성되면 내부 상태가 변하지 않는 클래스를 의미한다. 즉, 불변 클래스의 인스턴스는 생성된 후 그 상태를 변경할 수 없다.
- 1. 내부 상태 변경 불가능 : 불변 클래스의 객체는 생성된 후에는 내부의 상태를 변경할 수 없어 객체의 일관성과 안정성을 보장한다.
- 2. 쓰레드 안전성 : 불변 클래스는 여러 쓰레드에서 동시에 사용해도 안전하다. 여러 쓰레드가 동시에 접근하더라도 내부 상태가 변경되지 않기 때문에 동기화를 신경쓰지 않아도 된다.
- 3. 해시 키반 컬렉션 사용 가능 : 불변 클래스의 객체는 해시기반 컬렉션의 키로 사용이 가능하다. 객체의 해시코드가 변경되지 않기 때문에 안정적인 해시 기반 컬렉션의 동작을 보장한다.
- 4. 보안 강화 : 불변 클래스는 보안적인 측면에서도 유리하다. 예를 들어, 불변한 문자열 클래스는 문자열의 내용이 변경되지 않기 때문에 보안상의 문제를 방지할 수 있다.
대표적으로는 String이 불변 클래스이다. String 클래스는 기본적으로 String Pool을 이용해 객체 생성시 저장한다. 따라서 같은 값을 갖는 String 생성시(new 제외) 기존에 있다면 이를 반환한다. 또한 concat이나 +연산자를 통해 String 변경시 가변하는게 아닌 다른 객체를 만들어 저장하는 형태이다. 즉, 인스턴스가 계속 생긴다. 따라서 가변하게 하기위해서는 StringBuilder 또는 StringBuffer를 사용하는 것이 옳다.
빌더 패턴을 사용하자.
- 점층적 생성자 패턴의 안정성 + 자바 빈즈 패턴의 가독성을 겸비
- 클라이언트는 필요한 객체를 직접 만드는 것이 아닌, 필수 매개변수 만으로 생성자 또는 정적 팩토리를 통해 객체를 얻고 빌더 객체가 제공하는 일종의 세터들을 이용해 매개변수를 설정한다. 마지막으로 build 메서드를 통해 우리가 필요한 객체를 얻는다.
- 보통은 클래스 내부에 정적 멤버 클래스로 만들어 둔다.
코드 예시
- 아래 예시는 불변클래스이다. final로 설정해 한번 설정시 변경 불가능하게 설정한다.
- 한번 생성시 변경이 불가능하다. 만약 선택 매개변수를 입력받지 않아다면 0으로 설정되고 이 후, 변경할 수 없다.
- 세터 메서드들은 자신을 반환하기 때문에 연쇄적으로 사용 가능하다. (메서드 연쇄)
- 세터 메서드들을 사용하기에 가독성이 좋아진다.
- 유효성 검사는 빌더의 생성자와 세터들을 이용해 하면 된다.
public class NutrionFacts{
private final int fat;
private final int calories;
private final int size;
public static class Builder{
// 필수 매개변수
private final int fat;
private final int calories;
// 선택 매개변수는 기본값으로 설정
// 아직 불변이 아니므로 final을 사용하지 않음
private int size = 0;
public Builder(int fat, int calories){
this.fat = fat;
this.calories = calories;
}
public Builder size(int val){
size = val;
return this;
}
}
private NutrionFacts(Builder builder){
fat = builder.fat;
calories = builder.calories;
size = builder.size;
}
}
참고로 빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기도 좋다.
빌더 패턴의 단점
- 빌더 생성 비용이 크진않지만 비용이 드는 것은 당연하니 성능 고려시 참고해야한다.
- 매개변수가 적어도 4개 이상시 의미를 갖고 그전에는 오히려 생성자를 이용하는 것이 나을 수 있지만 API는 시간이 지날수록 매개변수가 늘어나는 경향이 있으니 같이 고려할 필요가 있다. 따라서 애초에 빌더로 시작하는 편이 나을 수 있다.
코드 정리
https://github.com/mokjaemin/EffectiveJAVA
'Language > Java Plus' 카테고리의 다른 글
Effective JAVA - Item5 : 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라. (0) | 2023.06.22 |
---|---|
Effective JAVA - Item4 : 인스턴스화를 막으려거든 private 생성자를 사용하라. (0) | 2023.06.10 |
Effective JAVA - Item3 : private 생성자나 열거 타입으로 싱클턴임을 보증하라. (0) | 2023.06.07 |
Effective JAVA - Item1 : 생성자 대신 정적 팩토리 메서드를 고려하라. (1) | 2023.05.16 |
Effective JAVA - 1. Introduction (0) | 2023.05.16 |