빌더 패턴은 복잡한 객체를생성하는 방법을 정의하는 클래스와 표현하는 방법을 정의하는 클래스를 별도로 분리하여, 서로 다른 표현이라도 이를 생헝할 수 있는 동일한 절차를 제공하는 패턴이다.
[빌더 패턴의 장점]
1. 필요한 데이터만 설정할 수 있다.
2. 유연성을 확보할 수 있다.
3. 가독성을 높일 수 있다.
4. 불변성을 확보할 수 있다.
[생성 패턴]
생성 패턴은 인스턴스를 만드는 절차를 추상화하는 패턴이다. 생성 패턴에 속하는 패턴들은 객체를 생성, 합성하는 방법이나 객체의 표현 방법을 시스템과 분리해준다. 생성 패턴은 시스템이 상속보다 복합방법을 사용하는 방향으로 진화되어가며 더 중요해지고 있다.
생성 패턴은 두 가지 중요한 이슈가 있다.
1. 생성 패턴은 시스템이 어떤 Concrete Class를 사용하는지에 대한 정보를 캡슐화한다.
2. 생성 패턴은 클래스의 인스턴스들이 어떻게 생성되고 결합되는지에 대한 부분을 완전히 가려준다.
즉, 생성패턴을 이용하면 무엇이 생성되고 누가 이것을 생성하며 이것이 어떻게 생성되는지 언제 생성할 것인지에 대한 유연성을 확보할 수 있게 된다.
위 빌더 패턴의 장점을 실제 예에서 적용시켜보자!
1. 필요한 데이터만 설정할 수 있다.
User객체를 생성하는데, height라는 속성이 필요없다고 생각해보자! 생성자나 정적 메소드를 이용하는 경우라면 height에 더미 값(0, null)을 넣거나 height가 없는 생성자를 만들어야 한다.
// 더미 값을 넣은 User객체
User user = new User("newtownboy", 10, 0);
// User.java
public class User {
private String name;
private int age;
private int height;
public void User(String name, int age) {
this.name = name;
this.age = age;
};
}
위 작업이 한 두번이면 괜찮지만 반복적인 경우, 시간 낭비로 이어질 수 있다. 다음은 빌더 패턴을 이용하여 해결해보자!
// 빌더 패턴을 적용한 User객체 생성
public class Main {
User user = User.builder().name("newtownboy").age(10).build();
}
이렇게 필요한 데이터만 설정할 수 있는 빌더 패턴의 장점은 생성자, 정적 메소드와 비교하여 테스트용 객체를 생성할 때 용이하게 해주고 불필요한 코드의 양을 줄이는 이점을 가져다준다.
2. 유연성을 확보할 수 있다.
위 예시에서 User객체에 몸무게를 나타내는 weight를 추가해야한다고 생각해보자. 그러면 우리는 새롭게 추가되는 변수 때문에 기존의 코드를 수정해야하는 상황에 직면하게 될 것이다.
하지만 빌더 패턴을 이용하면 새로운 변수가 추가되는 상황에 직면해도 기존 코드에 영향을 주지않을 수 있다.
// weight 추가
public class Main {
User user = User
.builder()
.name("newtownboy")
.age(10)
.height(180)
.weight(70)
.build();
}
빌더 패턴은 위와 같이 유연하게 객체의 값을 설정할 수 있도록 도와주기 때문에 기존의 코드를 수정할 필요가 없다.
3. 가독성을 높일 수 있다.
빌더 패턴을 사용하면 매개변수가 많아져도 가독성을 높일 수 있다. 생성자로 객체를 생성하는 경우에는 매개변수가 많이질수록 가독성이 떨어진다. 다음 예를 통해 알아보자.
// 가독성 예시
public class Main {
// 기존 생성자
User user = new User("newtownboy", 10, 100, 70);
// 빌더패턴 적용 생성자
User user = User
.builder()
.name("newtownboy")
.age(10)
.height(180)
.weight(70)
.build();
}
기존 생성자의 경우 해당 매개변수가 무엇을 의미하는지 파악하기 힘들다. 하지만 빌더 패턴을 적용하면 어떤 데이터에 어떤 값이 설정되는지 쉽게 파악할 수 있다.
4. 불변성을 확보할 수 있다.
클래스를 생성할 때 Setter를 사용한다. 하지만, Setter를 구현하는 것은 불필요하게 확장 가능성을 열어두는 것이다. 그렇기 때문에 클래스 변수를 final로 선언하고 객체의 생성은 빌더에 맡기는 것이 좋다.
1번의 코드를 다음과 같이 수정할 수 있다.
// User.java
@RequiredArgsConstructor
@Builder
public class User {
private final String name;
private final int age;
private final int height;
}
객체를 생성하는 대부분의 경우에는 빌더 패턴을 적용하는 것이 좋다. 하지만 엔티티 객체로부터 DTO를 생성하는 경우라면 MapStruct와 같은 라이브러리를 통해 생성을 위임하는 것이 좋다.