book/Effective java
[Effective java] item2 생성자에 매개변수가 많다면 빌더를 고려하라
it's woo
2021. 8. 31. 23:32
이펙티브 자바 3/E - 교보문고
프로그래밍인사이트 | 자바 6 출시 직후 출간된 『이펙티브 자바 2판』 이후로 자바는 커다란 변화를 겪었다. 그래서 졸트상에 빛나는 이 책도 자바 언어와 라이브러리의 최신 기능을 십분 활용
www.kyobobook.co.kr
개요
정적 팩터리와 생성자에는 선택적 매개변수가 많을때 적절히 대응하기 어렵다.
책에나온 3가지방법 점층적 생성자 패턴 , 자바빈즈 패턴 , 빌더 패턴 을 사용해보자
점층적 생성자 패턴
- 필수 매개변수만 받는 생성자,그리고 선택 매개변수를 1개씩 추가하여 받는 생성자들을 만든다.
- 원치 않는 매개변수까지 포함할때가 있는데 어쩔 수 없이 매개변수에 값을 지정해줘야 한다.
- 매개변수 개수가 많아지면 코드가 작성하기 어려워진다. (매개변수가 무엇인지 헷갈리게 된다.)
public class NutritionFacts {
private final int servingSize; // (mL, 1회 제공량) 필수
private final int servings; // (회, 총 n회 제공량) 필수
private final int calories; // (1회 제공량당) 선택
private final int fat; // (g/1회 제공량) 선택
private final int sodium; // (mg/1회 제공량) 선택
private final int carbohydrate; // (g/1회 제공량) 선택
//필수 매개변수를 받는 생성자
public NutritionFacts(int servingSize, int servings) {
this(servingSize, servings, 0);
}
//필수 매개변수와 선택 매개변수를 받는 생성자들
public NutritionFacts(int servingSize, int servings,
int calories) {
this(servingSize, servings, calories, 0);
}
//선택 매개변수의 개수만큼 메서드의 개수가 늘어난다.
public NutritionFacts(int servingSize, int servings,
int calories, int fat) {
this(servingSize, servings, calories, fat, 0);
}
//필수매개변수와 soduim 값만 설정할 수 없다. 어쩔수 없이 calories fat값도 지정해줘야 한다.
public NutritionFacts(int servingSize, int servings,
int calories, int fat, int sodium) {
this(servingSize, servings, calories, fat, sodium, 0);
}
public NutritionFacts(int servingSize, int servings,
int calories, int fat, int sodium, int carbohydrate) {
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}
public static void main(String[] args) {
NutritionFacts cocaCola =
new NutritionFacts(240, 8, 100, 0, 35, 27);
}
}
자바빈즈 패턴
- 매개변수가 없는 생성자로 객체를 만든 후, 세터 메서드들을 호출해 원하는 매개변수의 값을 설정하는 방식이다.
- 객체 하나를 만들려면 여러게의 메서드를 호출해야 한다.
- 객체가 완전히 생성되기 전까지는 일관성이 무너진 상테에 놓이게 된다.
- 인스턴스를 만들기 쉽고 가독성이 높다.
public class NutritionFacts {
// 매개변수들은 (기본값이 있다면) 기본값으로 초기화된다.
private int servingSize = -1; // 필수; 기본값 없음
private int servings = -1; // 필수; 기본값 없음
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
public NutritionFacts() { }
// Setters
//점층적 생성자 패턴보다 짧아 가독성이 좋다
public void setServingSize(int val) { servingSize = val; }
public void setServings(int val) { servings = val; }
public void setCalories(int val) { calories = val; }
public void setFat(int val) { fat = val; }
public void setSodium(int val) { sodium = val; }
public void setCarbohydrate(int val) { carbohydrate = val; }
public static void main(String[] args) {
//객체를 생성하고 세터를 통해 값을 설정한다.
NutritionFacts cocaCola = new NutritionFacts();
//객체 하나를 완성하기 위하여 5개의 메서드를 호출하였다.
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
}
}
빌더 패턴
- 필수 매개변수만으로 생성자(정적팩터리)를 호출해 빌더 객체를 얻는다.
- 그런 다음 빌더 객체가 제공하는 일종의 세터 메서드들로 원하는 선택 매개변수들을 설정한다.
- 마지막으로 매개변수가 없는 build 메서드를 호출해 필요한 객체를 얻는다.
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
//빌더를 사용하기 위하여 정적멤버 클래스를 생성하였다.
public static class Builder {
// 필수 매개변수
private final int servingSize;
private final int servings;
// 선택 매개변수 - 기본값으로 초기화한다.
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 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 Builder sodium(int val)
{ sodium = val; return this; }
public Builder carbohydrate(int val)
{ carbohydrate = 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;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
public static void main(String[] args) {
//빌더 메소드를 통해 연쇄적으로 메소드를 호출한다음 build()를 통해 객체의 값을 설정한다.
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100).sodium(35).carbohydrate(27).build();
}
}
빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 좋다.
공변 반환 타이핑은 뭘까?
하위 클래스의 메서드가 상위 클래스의 메서드가 정의한 반환 타입이 아닌, 그 하위 타입을 반환하는 기능을 말한다. 밑의 예시를 보자
class pizza{
pizza info() {
return new pizza();
}
}
class calzone extends pizza{
@Override
calzone info() {
return new calzone();
}
}
칼조네 피자는 info를 오버라이딩할때 반환형을 칼조네로 한다.
상속관계에 있어 하위타입으로 반환해도 문제가 없다. 이런 특징을 활용하여 형변환에 신경쓰지 않을 수 있다.
빌더 패턴의 단점
빌더 생성 비용이 크지는 않지만 성능에 민감한 상황에서는 문제가 될 수 있다.
점층적 생성자 패턴보다는 코드가 장황해서 매개변수가 4개이상은 되어야 값어치를 한다.