Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
Tags
- 알고리즘
- 수학
- 후니의 쉽게 쓴 시스코 네트워킹
- swea
- CS
- 구현
- Effective Java
- 그리디
- 이분탐색
- 문자열
- 플로이드-와샬
- 에라토스테네스의 체
- 스택
- 동적계획법
- 투 포인터
- 백준
- 세그먼트 트리
- BFS
- 위상정렬
- 완전탐색
- mst
- java
- Kotlin
- Network
- dfs
- JUnit 5
- 시뮬레이션
- 프로그래머스
- 백트래킹
- 유니온 파인드
Archives
반갑습니다!
[Effective Java] 아이템 2: 생성자에 매개변수가 많다면 빌더를 고려하라 본문
- 정적 팩터리와 생성자는 선택적 매개변수가 많을 때 적절히 대응하기 어렵다는 단점이 있다
대안 1: 점층적 생성자 패턴(telescoping constructor pattern)
- 점층적 생성자 패턴(telescoping constructor pattern)을 사용해서 해결할 수 있지만, 매개변수 개수가 더 늘어나면 클라이언트 코드를 작성하거나 가독성이 떨어진다는 한계가 있음
- ex) 원하는 매개변수를 모두 포함한 생성자 중에서 가장 짧은 것을 선택해야함
Nutritionfacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
- ex) 원하는 매개변수를 모두 포함한 생성자 중에서 가장 짧은 것을 선택해야함
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);
}
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;
}
}
대안 2: 자바빈즈 패턴(JavaBeans pattern)
- 또다른 대안으로 자바빈즈 패턴(JavaBeans pattern)이 있음
- 매개변수가 없는 생성자로 객체를 만든 후, 세터(setter) 메서드들을 호출해 원하는 매개변수의 값을 설정하는 방식
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() {
}
public void setServingSize(int servingSize) {
this.servingSize = servingSize;
}
public void setServings(int servings) {
this.servings = servings;
}
public void setCalories(int calories) {
this.calories = calories;
}
public void setFat(int fat) {
this.fat = fat;
}
public void setSodium(int sodium) {
this.sodium = sodium;
}
public void setCarbohydrate(int carbohydrate) {
this.carbohydrate = carbohydrate;
}
}
- 점증적 생성자 패턴보다 코드가 길어졌지만 인스턴스 만들기가 더 쉽고, 가독성이 좋음
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
단점
- 객체 하나를 만드려면 메서드를 여러 개 호출해야함
- 객체가 완전히 생성되기 전까지 일관성(consistency)이 무너진 상태에 놓임
- 클래스를 불변으로 만들 수 없어서 스레드 안정성을 얻기 위해 추가 작업이 필요함
- 단점을 완화하고자 생성이 끝난 객체를 수동으로 얼리고(freezing), 얼리기 전에는 사용할 수 없도록 처리하기도 함
- 다루기 어려워 실전에서는 거의 사용되지 않음
- 객체 사용 전에
freeze
메서드가 확실히 호출됐는지 컴파일러가 보증할 수 없음 -> 런타임 오류에 취약
대안 3: 빌더 패턴(Builder pattrern)
점층적 생성자 패턴의 안정성과 자바빈즈 패턴의 가독성을 겸비한 패턴
사용법
- 클라이언트는 필요한 객체를 직접 만드는 대신, 필수 매개변수만으로 생성자(혹은 정적 팩터리)를 호출해 빌더 객체를 얻음
- 빌더 객체가 제공하는 일종의 세터 메서드들로 원하는 선택 매개변수들을 설정함
- 마지막으로 매개변수가 없는
build
메서드를 호출해 객체를 얻음 (보통은 불변 객체)
일반적으로 빌더는 생성할 클래스 안에 정적 멤버 클래스로 만들어둠
- ex)
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();
- ex)
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 calories) {
this.calories = calories;
return this;
}
public Builder fat(int fat) {
this.fat = fat;
return this;
}
public Builder sodium(int sodium) {
this.sodium = sodium;
return this;
}
public Builder carbohydrate(int carbohydrate) {
this.carbohydrate = carbohydrate;
return this;
}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}
빌더 패턴은 명명된 선택적 매개변수(named optional parameters)를 흉내낸 패턴
유효성 검사
- 잘못된 매개변수를 최대한 일찍 발견하려면 빌더의 생성자와 메서드에서 입력 매개변수를 검사하고,
build
메서드가 호출하는 생성자에서 여러 매개변수에 걸친 불변식(invariant)를 검사하라 - 불변식을 보장하려면 빌더로부터 매개변수를 복사한 후 해당 객체 필드들도 검사해야함
- 검사해서 잘못된 점이 발견되면
IllegalArgumentException
을 던지면 됨- 불변식: 프로그램이 실행되는 동안, 혹은 정해진 기간 동안 반드시 만족해야 하는 조건
- ex)
List
의 크기는 음수가 될 수 없다,Period
클래스에서start
필드의 값이end
필드의 값보다 앞서야 함
- ex)
- 불변식: 프로그램이 실행되는 동안, 혹은 정해진 기간 동안 반드시 만족해야 하는 조건
- 잘못된 매개변수를 최대한 일찍 발견하려면 빌더의 생성자와 메서드에서 입력 매개변수를 검사하고,
빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기 좋음
- 각 계층의 클래스에 관련 빌더를 멤버로 정의하라
- 추상 클래스는 추상 빌더, 구체 클래스(concrete class)는 구체 빌더를 갖게함
- 각 계층의 클래스에 관련 빌더를 멤버로 정의하라
public abstract class Pizza {
public enum Topping {HAM, MUSHROOM, ONION, PEPPER, SAUSAGE}
final Set<Topping> toppings;
abstract static class Builder<T extends Builder<T>> {
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public T addTppings(Topping topping) {
toppings.add(Objects.requireNonNull(topping));
return self();
}
abstract Pizza build();
// 하위 클래스는 이 메서드를 재정의(overriding) 해서 "this"를 반환하도록 해야 한다.
protected abstract T self();
}
Pizza(Builder<?> builder) {
toppings = builder.toppings.clone();
}
}
- 추상 메서드
self
는 하위 클래스에서 형변환 하지 않고도 메서드 연쇄를 할 수 있도록 해주는데, 이를 시뮬레이트한 셀프 타입(simulated self-type) 관용구라고 함
public class NyPizza extends Pizza {
public enum Size {SMALL, MEDIUM, LARGE}
private final Size size;
public static class Builder extends Pizza.Builder<Builder> {
private final Size size;
public Builder(Size size) {
this.size = Objects.requireNonNull(size);
}
@Override
public NyPizza build() {
return new NyPizza(this);
}
@Override
protected Builder self() {
return this;
}
}
private NyPizza(Builder builder) {
super(builder);
size = builder.size;
}
}
public class Calzone extends Pizza {
private final boolean sauceInside;
public static class Builder extends Pizza.Builder<Builder> {
private boolean sauceInside = false; // 기본값
public Builder sauceInside() {
sauceInside = true;
return this;
}
@Override
public Calzone build() {
return new Calzone(this);
}
@Override
protected Builder self() {
return this;
}
}
private Calzone(Builder builder) {
super(builder);
sauceInside = builder.sauceInside;
}
}
- 각 하위 클래스 빌더가 정의한
build
메서드는 해당하는 구체 하위 클래스를 반환하도록 선언함 - 하위 클래스의 메서드가 상위 클래스의 메서드가 정의한 반환 타입이 아닌, 그 하위 타입을 반환하는 기능을 공변반환 타이핑(covariant return typing)이라함
- 공변반환 타이핑을 이용하면 클라이언트가 형변환에 신경 쓰지 않고도 빌더를 사용할 수 있음
단점
- 객체를 만들려면 그에 앞서 빌더부터 만들어야함
- 성능에 민감한 상황에서 문제가 될 수 있음
- 점층적 생성자 패턴보다는 코드가 장황해서 매개변수가 4개 이상은 되어야 값어치를 함
정리
생성자나 정적 팩터리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는게 낫다.
매개변수 중 다수가 필수가 아니거나 같은 타입이면 특히 더 낫다.
빌더는 점층적 생성자보다 클라이언트 코드를 읽고 쓰기가 훨씬 간결하고, 자바빈즈보다 훨씬 안전하다.
'개발' 카테고리의 다른 글
[Effective Java] 아이템 4: 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) | 2021.07.24 |
---|---|
[Effective Java] 아이템 3: private 생성자나 열거 타입으로 싱글턴임을 보증하라 (0) | 2021.07.23 |
[Effective Java] 아이템 1: 생성자 대신 정적 팩터리 메서드를 고려하라 (0) | 2021.07.22 |
[JUnit 5] JUnit 5 - 2 (0) | 2020.12.28 |
[JUnit 5] JUnit 5 - 1 (0) | 2020.12.27 |