본문 바로가기

Dev.BackEnd/JAVA

[DP] 2. 빌더 패턴(Builder Pattern)

#2. 빌더 패턴(Builder pattern)

인스턴스를 생성할 때, 생성자(Constructor)만을 통해서 생성하는데는 어려움이 있다.

빌더 패턴은 이 문제를 기반으로 고안된 패턴 중 하나이다. 예를 들면, 생성자 인자로 너무 많은 인자가 넘겨지는 경우 어떠한 인자가 어떠한 값을 나타내는지 확인하기 힘들다. 또 어떠한 인스턴스의 경우에는 특정 인자만으로 생성해야 하는 경우가 발생한다. 이럴 경우, 특정 인자에 해당하는 값을 null로 전달해줘야 하는데, 이는 코드의 가독성 측면에서 매우 좋지 않다는 것을 직감적으로 알 수 있다.

코드를 통해 확인해보자.

public Student(long id, String name, String major, int age, String address) {
this.id = id;
this.name = name;
this.major = major;
this.age = age;
this.address = address;
}

학생이라는 객체를 생성하는 코드이며, 이러한 생성자를 정의해두었다고 했을 때 클라이언트에서는, 

Student student = new Student(123456, "jbee", "CS", 25, "pankyo");

이런 식으로 인스턴스를 생성하게 된다.

그런데 학번과 이름만 아는 경우에는 어떻게 인스턴스를 생성할 수 있을까?

Student student = new Student(123456, "jbee", null, 0, null);

garbage data 또는 null값을 생성자 인자로 넘겨주어 이를 해결한다. 특정 파라미터가 어떠한 값을 나타내는지 확인하기 힘들다.


그렇다고 해서 점층적 생성자 패턴(telescoping constructor pattern)을 사용하게 되면 해당 클래스의 코드가 지저분해지고, 한 인스턴스를 생성할 때 사용하지 없는 코드들도 같이 생성되는 문제가 발생한다. 점층적 생성자 패턴이란 클래스 내에 오버로딩(Overloading)을 통해서 생성자를 여러 개 작성하는 것을 말한다.


그렇다면 setter 메서드를 사용한 자바 빈 패턴(Java Bean pattern)은 어떨까?

Student student = new Student();
student.setId(123456);
student.setName("jbee");
student.setAge(25);
student.setMajor("sample");
student.setAddress("pankyo");

클라이언트에서는 이런 식으로 객체를 생성하게 된다. 함수 호출 1회로 객체 생성을 끝낼 수 없으므로 객체 일관성(consistency)가 일시적으로 깨질 수 있다. 또한 immutable 객체를 생성할 수 없다.


점층적 생성자 패턴과 자바 빈 패턴의 장점을 결합한 것이 바로 빌더 패턴이다.

클라이언트 코드에서 필요한 객체를 직접 생성하는 대신, 그 전에 필수 인자들을 전달하어 빌더 객체를 만든 뒤, 빌더 객체에 정의된 설정 메서드들을 호출하여 인스턴스를 생성하는 것이다.


빌더 패턴을 적용하면 장점이 여러 가지 생긴다. 우선 인스턴스를 생성하는데 있어서 필수적인 인자와 선택적인 인자를 구별할 수 있다. 그리고 선택적인 인자의 경우, 보다 가독성이 좋은 코드로 인자를 넘길 수 있다. 이러한 장점들을 갖고 있으면서도 객체 일관성을 깨지 않을 수 있다.

코드를 통해 살펴보자.


public interface Buildable<T> {
T build();
}

일단 interface를 하나 만들어서 다른 builder 에도 적용할 수 있도록 할 수 있다.


public class Student {
private long id;
private String name;
private String major;
private int age;
private String address;

public Student() {}

public static class Builder implements Buildable {
//Essential parameter
private final long id;
private final String name;
//Selective parameter
private String major = "";
private int age = 0;
private String address = "";

public Builder(long id, String name) {
this.id = id;
this.name = name;
}

public Builder major(String major) {
this.major = major;
return this;
}

public Builder age(int age) {
this.age = age;
return this;
}

public Builder address(String address) {
this.address = address;
return this;
}

public Student build() {
return new Student(this);
}
}

private Student(Builder builder) {
this.id = builder.id;
this.name = builder.name;
this.major = builder.major;
this.age = builder.age;
this.address = builder.address;
}
}


Student 클래스안에 Builder라는 정적 멤버 클래스(static member class)를 하나 만들었다. 그리고 아까 만들어둔 interface를 implements 한다. 최종적으로 build()라는 메소드를 통해 인스턴스를 생성할 것이다. Student 클래스의 필드 중 id와 name을 필수 인자로 선택하여 final keyword를 추가하고, Builder를 생성할 때 인자로 넘겨 받게 하였다. 그리고 나머지 선택적 인자들은 Builder를 return 하게 하여 Chaining을 통해 인자를 넘겨받을 수 있도록 하였다. 클라이언트 코드를 확인해보자.

Student s1 = new Student.Builder(141096, "jbee")
.major("Web_Server_programming")
.age(25)
.address("Jeong-Ja")
.build();

여러 개의 파라미터를 전달하는데도 어떠한 값이 어떠한 의미인지 파악할 수 있으며, 필수 인자와 선택적 인자도 구별할 수 있게 되었다.


2. builder pattern end