객체 생성할 때 @Builder 를 많이 사용해서 궁금해서 공부했다.
정보처리기사 디자인 패턴 공부할 때 처음 접했었는데 실제로 이렇게 쓰임새를 알고 나니 재밌었다
객체 생성을 고려할 때는 필수적인 인자와 부수적인 인자를 생각해야 한다
예를 들어 집을 짓는다고 생각해보자
당구장이 있는 집과 수영장이 있는 집을 짓는다면 일단 집을 짓기 위한 재료는 필수적일 것이다
그리고 당구장을 짓기 위한 재료와 수영장을 짓기 위한 재료는 부수적인 재료이다
다음의 예제를 보자
점층적 생성자 패턴
public class House {
//필수적인 재료
private String brick;
private String cement;
public House(String brick, String cement) {
this.brick = brick;
this.cement = cement;
}
}
집을 짓기 위해서 벽돌과 시멘트가 필요하다면 이렇게 집을 지으면 된다
그럼 당구장이나 수영장을 짓기 위한 집은
package com.trips;
public class House {
private String brick;
private String cement;
private String billiards;
private String swimmingPool;
public House(String brick, String cement) {
this.brick = brick;
this.cement = cement;
}
public House(String brick, String cement, String billiards) {
this.brick = brick;
this.cement = cement;
this.billiards = billiards;
}
public House(String brick, String cement, String billiards, String swimmingPool) {
this.brick = brick;
this.cement = cement;
this.billiards = billiards;
this.swimmingPool = swimmingPool;
}
}
//일반 집
House house = new House(brick, cement);
//당구장이 있는 집
House houseWithBiliard = new House(brick, cement, billiards);
//수영장이 있는 집
House houseWithSP = new House(brick, cement, null, swimmingPool);
이렇게 될 것이다
이 코드의 단점은 수영장이 있는 집을 보면 당구장을 짓는 재료가 필요하지 않기 때문에 null로 들어갔고 코드를 보지 않으면 이 자리에 nul이 무엇인지도 알기가 어렵다. 이렇게 필요한 생성자를 일일이 다 만드는 패턴을 점층적 생성자 패턴이라고 한다.
이 패턴의 단점은 코드의 가독성이 굉장히 떨어지고 인자가 추가되면 일일이 수정하기가 어렵다는 것이다
자바빈 패턴
그래서 이를 보완하기 위해 자바빈 패턴이 나왔다
자바빈 패턴은 흔히 setter 메서드를 사용하는 것인데
public class House {
private String brick;
private String cement;
private String billiards;
private String swimmingPool;
public House(){
}
public void setBrick(String brick) {
this.brick = brick;
}
public void setCement(String cement) {
this.cement = cement;
}
public void setBilliards(String billiards) {
this.billiards = billiards;
}
public void setSwimmingPool(String swimmingPool) {
this.swimmingPool = swimmingPool;
}
이렇게 setter 메소드를 이용해서
House house = new House();
house.setBrick(brick);
house.setCement(cement);
house.setBilliards(billiards);
house.setSwimmingPool(swimmingPool);
이렇게 값을 일일이 넣어준다
이 패턴은 객체 생성 후에 값을 넣어줘 객체의 일관성이 깨지고 계속해서 값이 변한다는 단점이 있다
빌더 패턴
그래서 점층적 생성자 패턴과 자바 빈 패턴을 보완하기 위해 빌더 패턴이 나왔다
아래 코드를 보자
public class House {
private String brick;
private String cement;
private String billiards;
private String swimmingPool;
public class Builder {
private String brick;
private String cement;
private String billiards;
private String swimmingPool;
// 필수변수는 생성자로 값을 넣는다.
public Builder(String brick, String cement) {
this.brick = brick;
this.cement = cement;
}
public Builder billiards(String val) {
billiards = val;
return this;
}
public Builder swimmingPool(String val) {
swimmingPool = val;
return this;
}
public House build() {
return new House(this);
}
}
public House(Builder builder) {
this.brick = builder.brick;
this.cement = builder.cement;
this.billiards = builder.billiards;
this.swimmingPool = builder.swimmingPool;
}
}
이렇게 코드를 짜서
//수영장이 있는 집
House houseWithSP = new Builder("brick", "cement")
.swimmingPool(swimmingPool)
.build();
//당구장이 있는 집
House houseWithBilliards = new Builder("brick", "cement")
.billiards(billiards)
.build();
이렇게 선택해서 코드를 짤 수 있다.
확실히 점층적 생성자 패턴에 비해 필요한 인자만 사용할 수 있고 가독성도 좋다
하지만 위 코드 처럼 빌더 패턴을 만들기 위한 코드가 굉장히 길다 그래서 이를 보완하기 위해
@Builder 어노테이션을 이용해 간단하게 구현할 수 있다
@Builder
아래 코드를 보자
@Builder
public class House {
private String brick;
private String cement;
private String billiards;
private String swimmingPool;
@Builder
public House(String brick, String cement, String billiards, String swimmingPool) {
this.brick = brick;
this.cement = cement;
this.billiards = billiards;
this.swimmingPool = swimmingPool;
}
public static void main(String[] args) {
House houseWithSP = House.builder()
.brick("brick")
.cement("cement")
.swimmingPool("swimmingPool")
.build();
}
}
빌더 어노테이션을 통해 코드가 한결 줄어든 것을 알 수 있다. 코드를 보면 클래스와 생성자 둘다 어노테이션을 적용할 수 있다.
❗ 그러나 주의할 점은 클래스에 @Builder 어노테이션을 쓰지 말라는 것이다
Finally, applying @Builder to a class is as if you added @AllArgsConstructor(access = AccessLevel.PACKAGE) to the class and applied the @Builder annotation to this all-args-constructor. This only works if you haven't written any explicit constructors yourself. If you do have an explicit constructor, put the @Builder annotation on the constructor instead of on the class. Note that if you put both @Value and @Builder on a class, the package-private constructor that @Builder wants to generate 'wins' and suppresses the constructor that @Value wants to make.
출처 : https://projectlombok.org/features/Builder
요약 하자면 @Builder 를 클래스에 적용하는 것은 클래스에 @AllArgsConstructor를 적용한 것과 같다는 것이다 그래서 명백한 생성자를 사용하지 않았을 때만 적용되고 만약 생성자를 만들었다면 거기에 @Builder 를 적용하라고 한다.
참고
https://johngrib.github.io/wiki/pattern/builder/#fn:lombok-builder
https://sudo-minz.tistory.com/133