들어가기 앞서..
스터디를 진행하며, 사용한 코드에 대해 설명을 하기 위해 공부를 하다보니 @Builder를 사용하는 이유를 제대로 알지 못해서 찾아서 공부를 하게 되었다.
객체를 생성해주기 위해서는 생성자 패턴, 메소드 패턴, 수정자 패턴, 빌더 패턴 등을 사용할 수 있다. 많은 사람들이 빌더 패턴으로 객체를 생성하는 것을 선호하는 편인데 그러한 이유를 이번 포스팅을 통해 알아보겠다.
빌더 패턴을 사용해야 하는 이유는 무엇인가?
빌더 패턴의 사용이유를 간단하게 정리하면 다음과 같다.
- 필요한 데이터만 설정이 가능
- 코드의 유연성 확보
- 코드의 가독성 올라감
- 변경 가능성을 최소화
빌더 패턴을 사용해야 하는 이유를 아래의 간단한 코드를 통해 알아보겠다.
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private int age;
private int height;
private int weight;
}
1. 필요한 데이터만 설정이 가능
만약 위와 같은 User 객체를 생성해야 하는데, age 파라미터가 필요 없는 상황이라고 가정해보자.
이러한 경우는 2가지 경우로 대처할 수 있다.
1. 생성자나 정적 메소드를 이용하는 경우라면, 해당 age 파라미터에 해당하는 부분에 더미값을 넣어준다.
2. age가 없는 생성자를 새로 만들어준다.
// 1. 더미값을 넣어주는 방법
User user = new User("장수혁", 0, 180, 70)
------------------------------------------------
// 2. 생성자 또는 정적 메소드를 추가하는 방법
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private int age;
private int height;
private int weight;
public User (String name, int height, int weight) {
this.name = name;
this.height = height;
this.weight = weight;
}
public static User of(String name, int height, int weight) {
return new User(name, 0, 180, 70);
}
}
위와 같은 작업이 많아지거나, 자주 변경이 되거나, 실무에서 벌어지는 상황이라면 어떨까?
매번 개발자가 코드의 수정을 해주고, 자주 발생하게 된다면 이는 비용의 낭비를 초래하게 된다. 하지만 요구사항은 계속 변하게 되어있고, 반복적인 변경을 필요로 하게 되는 경우도 많다.
-> 아래의 코드처럼, Builder를 사용하게 된다면 해당 문제를 해결할 수 있다.
User user = User.builder()
.name("장수혁")
.height(180)
.weight(70)
.build();
2. 코드의 유연성 확보
위의 User 클래스에 MBTI를 넣어주는 새로운 변수 mbti가 추가된다고 가정해보자.
만약 이미 위처럼 코드가 작성이 되어있고, 객체 생성 코드 또한 작성되어 있다고 해보자. 그렇다면 mbti 라는 변수를 추가해주게 될 경우 기존의 코드를 수정해줘야 하는 번거로움이 있다.
// 기존 코드
User user = new User("장수혁", 26, 180, 70);
// 변경 코드
User user = new User("장수혁", 26, 180, 70, "istp");
위처럼 새롭게 추가되는 mbti 변수 때문에, 기존의 코드를 수정해줘야 하는 것이다. 만약 기존의 작성된 코드의 양이 방대하다면 아주 귀찮은 작업이 될 것이다. 또한 실수를 하게되면 오류가 발생할 수 있다.
-> 이러한 경우도, Builder 를 사용하게 되면 쉽게 해결할 수 있다.
이렇게 빌더 패턴을 사용하게 되면, 객체의 내용이 변경되어도 유연하게 값을 설정할 수 있다.
User user = User.builder()
.name("장수혁")
.height(180)
.weight(70)
.mbti("istp")
.build();
3. 코드의 가독성 올라감
객체 생성을 위한 파라미터가 아주 많다고 가정해보자.
생성자로 객체를 생성하는 경우를 생각해보면, 파라미터의 순서와 해당 값이 어떤 파라미터의 값을 의미하는지 보기 힘들 것이다. 즉, 가독성이 떨어진다는 것이다.
// 생성자에 적혀있는 값들이, 어떠한 파라미터의 값들을 의미하는지 코드 개발자외에는 확인하기 힘들 수 있다.
User user = new User("장수혁", 26, 180, 70, "istp");
하지만, 위에서 Builder 패턴을 사용한 코드를 보게 되면, 'name', 'age', 'height', 'weight', 'mbti' 처럼 파라미터를 명시해준 후, 값을 넣어주기에 코드를 쉽게 파악할 수 있고 가독성을 높일 수 있다.
4. 변경 가능성을 최소화
Setter를 사용하는 수정자 패턴을 사용하는 경우를 본 적 있을 것이다. 최근에는 변경의 가능성을 열어준다는 이유로 사용을 자제하는 추세이다. Setter를 구현한다는 것은 앞서 말했듯이 불필요하게 코드의 변경 가능성에 여지를 주는것이다.
혼자서 단독 개발하는 프로그램의 경우라면, 본인이 기억할 수 있기에, 추후 유지보수시에 코드를 수정하다 오류를 범하지 않을 수 있지만, 많은 개발자들과 동시에 프로젝트를 진행하게 된다면, 누군가가 Setter를 사용하여 불변해야할 변수의 값을 고쳐 오류가 발생할 수 있다.
-> 이러한 경우에 Builder 패턴을 사용하면 오류가 난 지점을 쉽게 찾을 수 있고, 방지도 할 수 있다.
(혹은 변수에 final을 붙여 선언하여 불변성을 확보할 수도있다.)
Builder 패턴 적용이 좋지 않은 예외의 경우는 없을까?
객체를 생성하는 대부분의 경우라면, 위와 같은 장점들로 Builder 패턴을 적용해주는 것이 좋다.
물론 예외의 경우도 존재하는데, 대표적으로 다음 2가지 상황에서는 빌더를 구현할 필요가 없다.
- 객체의 생성을 라이브러리로 위임하는 경우
- 변수의 개수가 2개 이하이며, 변경 가능성이 없는 경우
예를 들어 엔티티(Entity) 객체나 도메인(Domain) 객체로부터 DTO를 생성하는 경우라면 직접 빌더를 만들고 하는 작업이 번거로우므로 MapStruct나 Model Mapper와 같은 라이브러리를 통해 생성을 위임할 수 있다. 또한 변수가 늘어날 가능성이 거의 없으며, 변수의 개수가 2개 이하인 경우에는 정적 팩토리 메소드를 사용하는 것이 더 좋을 수도 있다. 빌더의 남용은 오히려 코드를 비대하게 만들 수 있으므로 변수의 개수와 변경 가능성 등을 중점적으로 보고 빌더 패턴을 적용할지 판단하면 된다.
들어가기 앞서..
스터디를 진행하며, 사용한 코드에 대해 설명을 하기 위해 공부를 하다보니 @Builder를 사용하는 이유를 제대로 알지 못해서 찾아서 공부를 하게 되었다.
객체를 생성해주기 위해서는 생성자 패턴, 메소드 패턴, 수정자 패턴, 빌더 패턴 등을 사용할 수 있다. 많은 사람들이 빌더 패턴으로 객체를 생성하는 것을 선호하는 편인데 그러한 이유를 이번 포스팅을 통해 알아보겠다.
빌더 패턴을 사용해야 하는 이유는 무엇인가?
빌더 패턴의 사용이유를 간단하게 정리하면 다음과 같다.
- 필요한 데이터만 설정이 가능
- 코드의 유연성 확보
- 코드의 가독성 올라감
- 변경 가능성을 최소화
빌더 패턴을 사용해야 하는 이유를 아래의 간단한 코드를 통해 알아보겠다.
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private int age;
private int height;
private int weight;
}
1. 필요한 데이터만 설정이 가능
만약 위와 같은 User 객체를 생성해야 하는데, age 파라미터가 필요 없는 상황이라고 가정해보자.
이러한 경우는 2가지 경우로 대처할 수 있다.
1. 생성자나 정적 메소드를 이용하는 경우라면, 해당 age 파라미터에 해당하는 부분에 더미값을 넣어준다.
2. age가 없는 생성자를 새로 만들어준다.
// 1. 더미값을 넣어주는 방법
User user = new User("장수혁", 0, 180, 70)
------------------------------------------------
// 2. 생성자 또는 정적 메소드를 추가하는 방법
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private int age;
private int height;
private int weight;
public User (String name, int height, int weight) {
this.name = name;
this.height = height;
this.weight = weight;
}
public static User of(String name, int height, int weight) {
return new User(name, 0, 180, 70);
}
}
위와 같은 작업이 많아지거나, 자주 변경이 되거나, 실무에서 벌어지는 상황이라면 어떨까?
매번 개발자가 코드의 수정을 해주고, 자주 발생하게 된다면 이는 비용의 낭비를 초래하게 된다. 하지만 요구사항은 계속 변하게 되어있고, 반복적인 변경을 필요로 하게 되는 경우도 많다.
-> 아래의 코드처럼, Builder를 사용하게 된다면 해당 문제를 해결할 수 있다.
User user = User.builder()
.name("장수혁")
.height(180)
.weight(70)
.build();
2. 코드의 유연성 확보
위의 User 클래스에 MBTI를 넣어주는 새로운 변수 mbti가 추가된다고 가정해보자.
만약 이미 위처럼 코드가 작성이 되어있고, 객체 생성 코드 또한 작성되어 있다고 해보자. 그렇다면 mbti 라는 변수를 추가해주게 될 경우 기존의 코드를 수정해줘야 하는 번거로움이 있다.
// 기존 코드
User user = new User("장수혁", 26, 180, 70);
// 변경 코드
User user = new User("장수혁", 26, 180, 70, "istp");
위처럼 새롭게 추가되는 mbti 변수 때문에, 기존의 코드를 수정해줘야 하는 것이다. 만약 기존의 작성된 코드의 양이 방대하다면 아주 귀찮은 작업이 될 것이다. 또한 실수를 하게되면 오류가 발생할 수 있다.
-> 이러한 경우도, Builder 를 사용하게 되면 쉽게 해결할 수 있다.
이렇게 빌더 패턴을 사용하게 되면, 객체의 내용이 변경되어도 유연하게 값을 설정할 수 있다.
User user = User.builder()
.name("장수혁")
.height(180)
.weight(70)
.mbti("istp")
.build();
3. 코드의 가독성 올라감
객체 생성을 위한 파라미터가 아주 많다고 가정해보자.
생성자로 객체를 생성하는 경우를 생각해보면, 파라미터의 순서와 해당 값이 어떤 파라미터의 값을 의미하는지 보기 힘들 것이다. 즉, 가독성이 떨어진다는 것이다.
// 생성자에 적혀있는 값들이, 어떠한 파라미터의 값들을 의미하는지 코드 개발자외에는 확인하기 힘들 수 있다.
User user = new User("장수혁", 26, 180, 70, "istp");
하지만, 위에서 Builder 패턴을 사용한 코드를 보게 되면, 'name', 'age', 'height', 'weight', 'mbti' 처럼 파라미터를 명시해준 후, 값을 넣어주기에 코드를 쉽게 파악할 수 있고 가독성을 높일 수 있다.
4. 변경 가능성을 최소화
Setter를 사용하는 수정자 패턴을 사용하는 경우를 본 적 있을 것이다. 최근에는 변경의 가능성을 열어준다는 이유로 사용을 자제하는 추세이다. Setter를 구현한다는 것은 앞서 말했듯이 불필요하게 코드의 변경 가능성에 여지를 주는것이다.
혼자서 단독 개발하는 프로그램의 경우라면, 본인이 기억할 수 있기에, 추후 유지보수시에 코드를 수정하다 오류를 범하지 않을 수 있지만, 많은 개발자들과 동시에 프로젝트를 진행하게 된다면, 누군가가 Setter를 사용하여 불변해야할 변수의 값을 고쳐 오류가 발생할 수 있다.
-> 이러한 경우에 Builder 패턴을 사용하면 오류가 난 지점을 쉽게 찾을 수 있고, 방지도 할 수 있다.
(혹은 변수에 final을 붙여 선언하여 불변성을 확보할 수도있다.)
Builder 패턴 적용이 좋지 않은 예외의 경우는 없을까?
객체를 생성하는 대부분의 경우라면, 위와 같은 장점들로 Builder 패턴을 적용해주는 것이 좋다.
물론 예외의 경우도 존재하는데, 대표적으로 다음 2가지 상황에서는 빌더를 구현할 필요가 없다.
- 객체의 생성을 라이브러리로 위임하는 경우
- 변수의 개수가 2개 이하이며, 변경 가능성이 없는 경우
예를 들어 엔티티(Entity) 객체나 도메인(Domain) 객체로부터 DTO를 생성하는 경우라면 직접 빌더를 만들고 하는 작업이 번거로우므로 MapStruct나 Model Mapper와 같은 라이브러리를 통해 생성을 위임할 수 있다. 또한 변수가 늘어날 가능성이 거의 없으며, 변수의 개수가 2개 이하인 경우에는 정적 팩토리 메소드를 사용하는 것이 더 좋을 수도 있다. 빌더의 남용은 오히려 코드를 비대하게 만들 수 있으므로 변수의 개수와 변경 가능성 등을 중점적으로 보고 빌더 패턴을 적용할지 판단하면 된다.