인스턴스를 생성할 때, 여러 인자의 값을 초기화 해줘야 할 때가 있다. 이번 규칙에서는 세 가지 패턴을 비교해 효율적인 방법을 제시한다.
점층적 생성자 패턴(telescoping constructor pattern)
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 32 33 34 | public class NutritionFacts { private final int SERVING_SIZE; private final int SERVINGS; private final int CALORIES; private final int FAT; private final int SODIUM; private final int CARBOHYDRATE; 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.SERVING_SIZE = servingSize; this.SERVINGS = servings; this.CALORIES = calories; this.FAT = fat; this.SODIUM = sodium; this.CARBOHYDRATE = carbohydrate; } } | cs |
위 클래스를 인스턴스화 하기 위해서는 아래와 같이 인자 개수에 맞는 생성자를 호출하면 된다.
1 | NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 3, 35, 27); | cs |
하지만, 이런 방식에는 문제가 있다. 인자의 수가 많거나, 설정할 필요가 없는 인자도 전달해야하는 상황이 생긴다. 그리고 생성자를 호출한 코드에서 매개변수가 어떤 인자를 초기화하는지 알기 힘들다. 예를들어, 지금 매개변수 100이 어떤 인자를 초기화할까? 한 번에 대답하기 힘들 것이다.
책에서 요약한 내용은 다음과 같다.
점층적 생성자 패턴은 잘 동작하지만 인자 수가 늘어나면 클라이언트 코드를 작성하기가 어려워지고, 무엇보다 읽기 어려운 코드가 되고만다.
자바 빈 패턴(java bean pattern)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 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; } } | cs |
위 클래스를 인스턴스화 하기 위해선 생성자를 호출하면된다. 하지만 실제로 쓸모있게? 사용하려면 setter를 이용해 값을 초기화 시켜줘야한다.
1 2 3 4 5 6 7 | NutritionFacts cocaCola = new NutritionFacts(); cocaCola.setServingSize(240); cocaCola.setSevings(8); cocaCola.setCalories(100); cocaCola.setSodium(35); cocaCola.setCarbohydrate(27); | cs |
코드의 길이는 조~금 길지만 가독성은 좋다. 객체 생성하기도 쉽고! 하.지.만 이 방식도 문제가 있다.
바로 변경 불가능한 클래스를 만들 수 없다는 것이다! 만약 위 클래스에 private final int CONSTANT; 라는 인자가 있을 때, 초기화를 시켜줄 수 있을까?
책에서 지적한 내용은 다음과 같다.
1회의 함수 호출로 객체 생성을 끝낼 수 없으므로, 객체 일관성이 일시적으로 깨질 수 있다.
자바빈 패턴으로는 변경 불가능 클래스를 만들 수 없다는 것이다.
빌더 패턴(builder pattern)
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | package rule02; public class NutritionFact { private final int SERVING_SIZE; private final int SERVINGS; private final int CALORIES; private final int FAT; private final int SODIUM; private final int CARBOHYDRATE; private int variable; public int getVariable() { return variable; } public void setVariable(int variable) { this.variable = variable; } public static class Builder { //필수인자 private final int SERVING_SIZE; 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.SERVING_SIZE = servingSize; this.SERVINGS = servings; } public Builder calories(int val) { CALORIES = val; return this; } public Builder fat(int val) { FAT = val; return this; } public Builder carbohydrate(int val) { CARBOHYDRATE = val; return this; } public Builder sodium(int val) { SODIUM = val; return this; } public NutritionFact build() { return new NutritionFact(this); } } private NutritionFact(Builder builder) { SERVING_SIZE = builder.SERVING_SIZE; SERVINGS = builder.SERVINGS; CALORIES = builder.CALORIES; FAT = builder.FAT; SODIUM = builder.SODIUM; CARBOHYDRATE = builder.CARBOHYDRATE; } } | cs |
위 클래스는 필요한 객체를 직접 만들지 않고, 필수 인자들을 생성자에 전달해 빌더 객체(Builder)를 만든다. 그리고 빌더 객체의 메서드를 호출해 선택적 인자들을 추가한다. 마지막으로 build()메서드를 호출해 변경 불가능한 클래스를 만든다.
실제로 위 클래스를 객체로 만드는 코드는 다음과 같다.
1 2 | NutritionFact cocaCola = new NutritionFact.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build(); cocaCola.setVariable(12); | cs |
정말, 간결하고, 직관적인 코드가 아닐 수가 없다. 나는 이 코드를 보고 충격을 먹었다.
그리고 앞으로 적극적으로 빌더 패턴을 사용할 것을 다짐했다.
빌더 패턴은 인자가 많은 생성자나 정적 팩터리가 필요한 클래스를 설계할 때, 특히 대부분의 인자가 선택적 인자인 상황에 유용하다.
'☕️ JAVA' 카테고리의 다른 글
Spring AOP와 AspectJ 비교하기 (14) | 2019.12.15 |
---|---|
이미지를 BLOB 형태로 DB에 저장하기 (5) | 2018.09.09 |
웹 서버와 웹 어플리케이션 서버의 차이 (1) | 2017.12.22 |
예외(Exception) - 잘못된 처리들 (0) | 2017.12.04 |
[이펙티브자바] 규칙05 : 불필요한 객체는 만들지 말라 (0) | 2017.10.10 |