프로젝트를 하던 중 만난 @Builder.. 사용하니 테스트 코드를 작성하는데 굉장히 유용하여 따로 정리해보려고 한다.
객체를 정의하고 그 객체를 생성할 때 보통 생성자를 통해 생성하는 것을 생각한다. 하지만 생성자를 통해 객체를 생성하는데 몇 가지 단점이 있어 객체를 생성하는 별도 builder를 두는 방법이 있다.
// 생성자를 통해 생성 Bag bag = new Bag("name", 1000, "memo");
Bag bag = Bag.builder() .name("name") .money(1000) .memo("memo") .build();
객체를 생성할 수 있는 빌더를 builder() 함수를 통해 얻고 거기에 셋팅하고자 하는 값을 셋팅하고 마지막에 build()를 통해 빌더를 작동시켜 객체를 생성한다.
// 많으면 뭐가 뭔지 알기 어렵다. Bag bag = new Bag("name", 1000, "memo", "abc", "what", "is", "it", "?");
하지만 이를 빌더 패턴으로 구현하면 각 값들은 빌더의 각 값들의 이름 함수로 셋팅이 되지 각각 무슨 값을 의미하는지 파악하기 쉽다. 따라서, 생성자로 설정해야하는 값이 많을 경우 빌더를 쓰는 것이 생성자보다 가독성이 좋다.
Bag bag = Bag.builder() .name("name") .money(1000) .memo("memo") .letter("This is the letter") .box("This is the box") .build();
생성자의 경우는 정해진 파라미터 순서대로 꼭 값을 넣어줘야 한다. 순서를 무시하고 값을 넣으면 에러가 발생하거나 엉뚱한데 값이 들어갈 수 있다.
public Bag(String name, int money, String memo) { this.name = name; this.money = money; this.memo = memo; }
하지만, 빌더 패턴은 빌더의 필드 이름으로 값을 설정하기 때문에 순서에 종속적이지 않다. 어떤 필드를 먼저 설정해야하는지 굳이 순서를 생각할 필요 없이 설정하면 된다.
Bag bag = Bag.builder() .name("name") .memo("memo") // memo를 money 대신 먼저! .money(1000) .build();
@Builder
- 위와 같은 이유로 생성자 대신 빌더를 사용하곤 하는데 롬복의 @Builder 어노테이션은 아래와 같이 멤버 필드들에 대해 빌더를 만들어준다.
// Before: @Builder class Example<T> { private T foo; private final String bar; } // After: class Example<T> { private T foo; private final String bar; private Example(T foo, String bar) { this.foo = foo; this.bar = bar; } public static <T> ExampleBuilder<T> builder() { return new ExampleBuilder<T>(); } public static class ExampleBuilder<T> { private T foo; private String bar; private ExampleBuilder() {} public ExampleBuilder foo(T foo) { this.foo = foo; return this; } public ExampleBuilder bar(String bar) { this.bar = bar; return this; } @java.lang.Override public String toString() { return "ExampleBuilder(foo = " + foo + ", bar = " + bar + ")"; } public Example build() { return new Example(foo, bar); } } }
사용법은 아주 간단하다. 빌더 패턴을 적용할 객체에 @Builder 어노테이션을 달기만 하면 된다.
@Builder public class Bag { private String name; private int money; private String memo; }
어노테이션만 달면 빌더가 생기고 위에 썼던 것과 같이 빌더를 통해 객체를 생성할 수 있다.
Bag bag = Bag.builder() .name("name") .money(1000) .memo("memo") .build();