이펙티브 자바 Item 2 - 생성자에 매개변수가 많다면 빌더를 고려하라
2021, Mar 07
생성자에 매개변수가 많다면 빌더를 고려하라
선택적 매개변수가 많을 때 해결하는 방법
-
점층적 생성자 패턴
-
필수 매개변수를 만드는 생성자부터 선택적 매개변수를 만드는 생성자를 하나씩 늘려가는 방법
-
예시
public class GameStat { private final String nickname; // 필수 private final int healthPoint; // 필수 private final int manaPoint; // 선택 private final float power; // 선택 public GameStat(String nickname, int healthPoint) { this.nickname = nickname; this.healthPoint = healthPoint; } public GameStat(String nickname, int healthPoint, int manaPoint) { this.nickname = nickname; this.healthPoint = healthPoint; this.manaPoint = manaPoint; } public GameStat(String nickname, int healthPoint, int manaPoint, int power) { this.nickname = nickname; this.healthPoint = healthPoint; this.manaPoint = manaPoint; this.power = power; } }
- 위의 방법처럼 점층적 생성자 패턴은 매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다.
- 실수로 매개변수의 순서를 바꿔 건네주더라도 컴파일러는 알아채지 못하고, 결국 런타임에 엉뚱한 동작을 하게 된다.
-
-
자바빈즈 패턴(JavaBeans pattern)
-
매개 변수가 없는 생성자로 객체를 만든 후, 세터(setter) 메서드들을 호출해 원하는 매개변수의 값을 설정하는 방식이다.
-
예시
public class GameStat { private final String nickname; // 필수 private final int healthPoint; // 필수 private final int manaPoint; // 선택 private final float power; // 선택 public GameStat() { } public void setNickname(String nickname) { this.nickname = nickname; } public void setHealthPoint(int healthPoint) { this.healthPoint = healthPoint; } public void setManaPoint(int manaPoint) { this.manaPoint = manaPoint; } public void setPower(float power) { this.power = power; } } public class Main { public static void main(String[] args) { GameStat gameState = new GameStat(); gameStat.setNickname("닉네임"); gameStat.setHealthPoint(100); gameStat.setManaPoint(50); gameStat.setPower(5.0f); } }
- 위와 같은 자바빈즈는 자신만의 심각한 단점을 지니고 있다.
- 자바빈즈 패턴에서는 객체 하나를 만들려면 메서드를 여러 개 호출해야 하고, 객제가 완전히 생성되기 전까지는 일관성(consistency)이 무너진 상태에 놓이게 된다.
- 일관성이 무너지는 문제 때문에 자바빈즈 패턴에서는 클래스를 불변(Item17)으로 만들 수 없으며, 스레드 안전성을 얻으려면 프로그래머가 추가 작업을 해줘야만 한다.
-
-
빌더 패턴(Builder pattern)
-
점층적 생성자 패턴의 안전성과 자바빈즈 패턴의 가독성을 겸비한 빌더 패턴을 사용하여 위의 문제를 해결할 수 있다.
-
예시
public class GameStat { private final String nickname; private final int healthPoint; private final int manaPoint; private final float power; public static class Builder { // 필드 매개변수 private String nickname; private int healthPoint; // 선택 매개변수 (기본값으로 초기화한다.) private int manaPoint = 0; private float power = 0f; public Builder(final String nickname, final int healthPoint) { this.nickname = nickname; this.healthPoint = healthPoint; } public Builder manaPoint(final int manaPoint) { this.manaPoint = manaPoint; return this; } public Builder power(final float power) { this.power = power; return this; } public GameStat build() { return new GameStat(this); } } private GameStat(Builder builder) { nickname = builder.nickname; healthPoint = builder.healthPoint; manaPoint = builder.manaPoint; power = builder.power; } } public class Main { public static void main(String[] args) { GameStat gameStat = new GameStat.Builder("닉네임", 100) .manaPoint(50) .power(5.0f) .build(); } }
- 이 클라이언트 코드는 쓰기 쉽고, 무엇보다도 읽기 쉽다.
- 빌더 패턴은 명명된 선택적 매개변수(named optional parameters)를 흉내 낸 것이다.
-
-
빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 좋다.
- 해당 내용은 심화된 내용이므로 추후에 다시 작성하도록 하겠다.