빌더 패턴에 대한 간략한 개념과 코드를 블로그에 포스팅 한 것이 있다. 그 글을 읽고 나서 아래 글을 읽는 것이 더 좋을 듯하여 링크를 남긴다.
디자인 패턴 - 빌더 패턴(Builder Pattern)
정적 팩터리나 생성자는 같은 문제를 갖고 있다. 클라스 안에 인자가 많은 상황에 잘 적응하지 못한다는 것. 아래 Person 클래스가 있다. 이런 클래스에는 어떤 생성자나 정적 팩터리 메서드가 적합할까? 보통 개발자는 아래의 예제 코드와 같이 점층적 생성자 패턴을 적용한다. 필수 인자만 받는 생성자를 하나 정의하고, 선택적 인자를 하나 받는 생성자를 추가하고, 또 추가하고 계속 추가하여 쌓아 올리는 것처럼 추가하는 것이다.
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 | class Person { private String name; //필수 private String age; //필수 private String sex; private String phoneNumber; private String address; private String job; public Person(String name) { this(name, ""); } public Person(String name, String age) { this(name, age, ""); } public Person(String name, String age, String sex) { this(name, age, sex, ""); } public Person(String name, String age, String sex, String phoneNumber) { this(name, age, sex, phoneNumber, ""); } public Person(String name, String age, String sex, String phoneNumber, String address) { this(name, age, sex, phoneNumber, address, ""); } public Person(String name, String age, String sex, String phoneNumber, String address, String job) { this.name = name; this.age = age; this.sex = sex; this.phoneNumber = phoneNumber; this.address = address; this.job = job; } } |
하지만 문제가 있다. 필수로 들어가는 이름과 나이를 제외하고 나머지 선택 인자에는 빈 스트링("")이 들어가게 된다. 요약하자면 점층적 생성자 패턴은 잘 동작하지만 인자 수가 늘어나면 클라이언트 코드를 작성하기가 어려워지고 무엇보다 읽기 어려운 코드가 되고 만다.
생성자에 전달하는 인자 수가 많을 때 적용 가능한 두 번째 대안은 자바빈 패턴이다. 인자 없는 생성자를 호출하여 객체부터 만든 다음, 설정 메서드(Setter)을 이용하여 값을 채우는 것이다. 하지만 심각한 단점이 있다. 1회의 함수호출로 객체 생성을 끝낼 수 없으므로, 객체 일관성이 일시적으로 깨질 수 있다는 것이다. 또한 자바빈 패턴으로는 변경 불가능한 클래스를 만들 수가 없다.
다행히 점층적 생성자 패턴의 안전성에 자바빈 패턴의 가독성을 결합한 세 번째 대안이 있는데 바로 빌더(Builder) 패턴이다. 필요한 객체를 직접 생성하는 대신, 클라이언트는 먼저 필수 인자들을 생성자에 전부 전달하여 빌더 객체를 만든다. 그런 다음 빌더 객체에 정의된 설정 메서드들을 호출하여 선택적 인자들을 추가해 나간다. 그리고 마지막으로 아무 인자 없이 build 메서드를 호출하여 변겨 불가능 객체를 만드는 것이다. 빌더 클래스는 빌더가 만드는 객체 클래스의 정적 멤버 클래스로 정의한다.
아무리 떠들어봤자 예제 코드 한번 보면 다 이해가 갈 것이다. 위 링크 예제와는 조금 다르게 빌더 패턴을 구현하였으니 두개다 보는 것이 더 좋을 것 같다.
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 | class Person { private final String name; private final String age; private final String sex; private final String phoneNumber; private final String address; private final String job; public static class Builder { private String name = ""; private String age = ""; private String sex = ""; private String phoneNumber = ""; private String address = ""; private String job = ""; public Builder(String name, String age) { this.name = name; this.age = age; } public Builder setSex(String sex) { this.sex = sex; return this;} public Builder setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; return this;} public Builder setAddress(String address) { this.address = address; return this;} public Builder setJob(String job) { this.job = job; return this;} public Person build() { return new Person(this); } } public Person(Builder builder) { this.name = builder.name; this.age = builder.age; this.sex = builder.sex; this.phoneNumber = builder.phoneNumber; this.address = builder.address; this.job = builder.job; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + ", sex=" + sex + ", phoneNumber=" + phoneNumber + ", address=" + address + ", job=" + job + "]"; } } |
1 2 | Person person = new Person.Builder("ktko", "30").setJob("Programmer").setAddress("Seoul").build(); System.out.println(person.toString()); //Person [name=ktko, age=30, sex=, phoneNumber=, address=Seoul, job=Programmer] |
빌더 패턴에도 단점은 있다. 객체를 생성하려면 우선 빌더 객체를 생성해야 한다. 실무에서 빌더 객체를 만드는 오버헤드의 문제가 될 소지는 없어 보이지만, 성능이 중요한 상황에선 그렇지 않을 수도 있다. 또한 빌더 패턴은 점층적 생성자 패턴보다 많은 코드를 요구하기 때문에 인자가 충분히 많은상황에서 이용해야 한다. 인자가 적더라도, 나중에 새로운 인자를 추가해야 할 상황이 올 수 있음을 기억하자.
요약하자면 빌더 패턴은 인자가 많은 생성자나 정적 팩터리가 필요한 클래스를 설계할 때, 특히 대부분의 인자가 선택적 인자인 상황에 유용하다.
디자인 패턴에서 정리한 내용의 코드와 Effective java책을 읽고
작성한 코드 두 개다 구현해보고 비교해보면서좀더 깊이 이해할 수 있게 되었다.
디자인 패턴 카테고리에 있는 빌더패턴과 이 글의 내용을 같이 보는 것을 추천한다.
'Effective Java > 1장 객체의 생성과 삭제' 카테고리의 다른 글
Effective Java #6 유효기간이 지난 객체 참조는 폐기하라 (0) | 2018.10.20 |
---|---|
Effective Java #5 불필요한 객체는 만들지 마라 (0) | 2018.10.20 |
Effective Java #4 객체 생성을 막을 때는 private 생성자를 사용하라 (0) | 2018.10.18 |
Effective Java #3 private 생성자나 enum 자료형은 싱글턴 패턴을 따르도록 설계하라 (0) | 2018.10.18 |
Effective Java #1 생성자 대신 정적 팩토리 메서드를 사용할 수 없는지 생각해 보라 (0) | 2018.10.16 |