계속 @Builder 패턴을 익혀야겠다는 찰나에 팀장으로서 매우매우 유능한 팀원 두명을 만나 여러가지를 같이 하게 되다보니 나도 뭔가 더 알려주고 싶다는 생각이 들어서, 좀더 열심히 배우게 되었다.
어제는 @Query에 대한 정보를 공유했고 오늘은 @Builder에 대한 내용을 정리하여 같이 다뤄볼까한다
왜 @Builder 패턴을 써야할까
1. 필요한 데이터만 설정할 수 있다
2. 유연성을 확보할 수 있다
3. 가독성을 높일 수 있다
4. 변경 가능성을 최소화할 수 있다
1.필요한 데이터만 설정할 수 있다
예를 들어 User 객체를 생성해야 하는데 age라는 파라미터가 필요 없는 상황이라고 가정하자.
생성자나 정적 메소드를 이용하는 경우라면 우리는 age에 더미 값을 넣어주거나 age가 없는 생성자를 새로 만들어주어야 한다.
// 1. 더미값을 넣어주는 방법
User user = new User("우당탕탕", 0, 180, 150)
// 2. 생성자 또는 정적 메소드를 추가하는 방법
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private int age;
private int height;
private int iq;
public User (String name, int height, int iq) {
this.name = name;
this.height = height;
this.iq = iq;
}
public static User of(String name, int height, int iq) {
return new User(name, 0, 180, 150);
}
}
이러한 작업이 한 두번이면 괜찮아도,요구사항이 지속적으로 변하게 되면 시간 낭비로 이어지게 된다.
이럴때 빌더를 사용하면 동적으로 처리가 가능하다.
User user = User.builder()
.name("우당탕탕")
.height(180)
.iq(150).build();
그리고 이렇게 필요한 데이터만 설정할 수 있는 빌더의 장점은 생성자 또는 정적 메소드와 비교하여 테스트용 객체를 생성할 때 용이하게 해주고,불필요한 코드의 양을 줄이는 등의 이점을 안겨준다.
2.유연성을 확보할 수 있다
예시로 아래와 같은 User클래스에 무게를 나타내는 변수 weight를 추가해야한다고 하자. 하지만 이미 다음과 같은 생성자로 객체를 만드는 코드가 있다고 해보자
// ASIS
User user = new User("우당탕탕", 28, 180, 150)
// TOBE
User user = new User("우당탕탕", 28, 180, 150, 75)
새로운 변수 때문에 기존의 코드를 수정해야 하는 상황에 직면하게 된다. 기존에 작성된 코드가 방대하다면 감당하기 어려울 수 있다. 하지만 빌더 패턴을 이용하면 새로운 변수가 추가되는 등의 상황이 생겨도 기존의 코드에 영향을 주지 않을 수 있다.
@Test
public void 1번테스트() {
// 수정 필요함 (ASIS)
User user = new User("우당탕탕", 28, 180, 150);
// 수정 필요함 (TOBE)
User user = new User("우당탕탕", 28, 180, 150, 75);
...
}
...
@Test
public void 100번테스트() {
// 수정 필요함 (ASIS)
User user = new User("우당탕탕", 28, 180, 150);
// 수정 필요함 (TOBE)
User user = new User("우당탕탕", 28, 180, 150, 75);
...
}
만약 위와 같이 User 객체를 생성하는 코드가 100개 있다면 모든 로직을 수정해주거나 생성자를 따로 추가하는 등의 불 필요한 조치를 해주어야 할 것이다. 하지만 빌더 패턴을 기반으로 코드가 작성되어 있다면 기존의 코드는 수정할 필요가 없게 된다. 빌더 패턴의 유연함이 객체의 값 설정에 도움을 주기 때문이다
3. 가독성을 높일 수 있다
빌더 패턴을 사용하면 매개변수가 많아져도 가독성을 높일 수 있다.생성자로 객체를 생성하는 경우에는 매개변수가 많아질수록 코드 리딩이 급격하게 떨어진다.
User user = new User("우당탕탕", 28, 180, 150)
위와 같은 코드를 보면 28와 180 또는 150이 무엇을 의미하는지 바로 파악이 힘들고, 클래스 변수가 4개 이상만 되어도 코드를 읽기 힘들어진다. 하지만 다음과 같이 빌더 패턴을 적용하면 직관적으로 어떤 데이터에 어떤 값이 설정되는지 쉽게 파악하여 가독성을 높일 수 있다.
User user = User.builder()
.name("우당탕탕")
.age(28)
.height(180)
.iq(150).build();
4. 변경 가능성을 최소화할 수 있다
많은 개발자들이 수정자 패턴(Setter)를 흔히 사용한다. 하지만 Setter를 구현한다는 것은 불필요하게 변경 가능성을 열어 두는 것이다. 이는 유지보수 시에 값이 할당된 지점을 찾기 힘들게 만들며 불필요한 코드 리딩 등을 유발한다. 만약 값을 할당하는 시점이 객체의 생성뿐이라면 객체에 잘못된 값이 들어왔을 때 그 지점을 찾기 쉬워 유지보수성이 훨씬 높아진다.그렇기에 클래스 변수는 변경 가능성을 최소화하는 것이 좋다.
변경 가능성을 최소화하는 가장 좋은 방법은 변수를 final로 선언함으로써 불변성을 확보하는 것이다.
(불변성을 확보해야한다)
@Builder
@RequiredArgsConstructor
public class User {
private final String name;
private final int age;
private final int height;
private final int iq;
}
하지만 경우에 따라서 클래스 변수에 final을 붙일 수 없는 경우가 있을 수 있다. 이러한 경우라면 final이 없어도 Setter를 구현하지 않음으로써 동일한 효과를 얻을 수 있다. 중요한 것은 변경 가능성을 열어두지 않는 것이나,final로 강제할 수 있다면 가장 바람직하지만 final을 붙일 수 없는 경우로 Setter를 넣어주지 않으면 된다.
@Builder
@AllArgsConstructor
public class User {
private String name;
private int age;
private int height;
private int iq;
}
객체를 생성하는 대부분의 경우에는 빌더 패턴을 적용하는 것이 좋다. 물론 예외적인 케이스가 있을 수 있는데, 대표적으로 크게 다음의 2가지 상황에서는 빌더를 구현할 필요가 없다
- 객체의 생성을 라이브러리로 위임하는 경우
- 변수의 개수가 2개 이하이며, 변경 가능성이 없는 경우
예를 들어 엔티티(Entity)객체나 도메인(Domain)객체로부터 DTO를 생성하는 경우라면 직접 빌더를 만들고 하는 작업이 번거로우므로 MapStruct나 Model Mapper와 같은 라이브러리를 통해 생성을 위임할 수 있다.또한 변수가 늘어날 가능성이 거의 없으며,변수가 늘어날 가능성이 거의 없고 변수가 2개 이하일 경우 에는 정적 팩토리 메소드를 사용하는게 좋다.
빌더 남용은 오히려 코드가 비대해져 변수의 개수와 변경가능성 등을 중점적으로 보고 빌더 패턴을 적용할지 판단하자
다른 사람과 협업을 하거나 변경에 대한 요구 사항이 많을 때는 빌더 패턴의 필요성이 극명해진다.
아직 객체 생성을 위해 생성자나 수정자 패턴,정적 메소드 패턴을 사용한다면 롬복을 사용해 빌더 패턴을 적용하자
참고 자료 :
https://mangkyu.tistory.com/163
https://readystory.tistory.com/121
https://onlyfor-me-blog.tistory.com/432
'책벌레와 벌레 그 사이 어딘가 > 개념쌓기' 카테고리의 다른 글
[개념쌓기]SSL? HTTPS? (0) | 2022.08.25 |
---|---|
[개념쌓기]NginX? + 설치까지 (0) | 2022.08.23 |
[개념쌓기] - JPA : @Query (0) | 2022.08.22 |
[개념쌓기] CORS-크로스 도메인_프로토콜 문제 (0) | 2022.08.19 |
[개념쌓기]CASCADE?OrphanRemoval? (0) | 2022.08.18 |
댓글