Lombok을 사용하면서 @Builder 어노테이션을 꽤 많이 사용하게 된다.
교육기관에서는 별 다른 이유 없이 "편하니까" 사용하라는 식으로, 별 다른 설명 없이 @Builder를 사용하게끔 종용한다.
개인적으로는 Setter를 비하기 위해 Builder를 사용하자 라는 글을 보고, 정말 특별한 이유가 없다면 최대한 Builder를 사용해서 객체를 생성하게 되었다.
이펙티브 자바에 보면 왜 Builder를 사용해야 하는지 이유를 알려주고 있다.
1. 매개 변수가 많은데 생성자 패턴을..?
필드에 변수가 적다면 생성자를 이용하여 객체를 생성할 수 있다.
public class ConstructorEx {
public class ConstructorEx {
private final String Hello;
private final String this_;
public ConstructorEx(String hello, String this_) {
Hello = hello;
this.this_ = this_;
}
}
}
위와 같이 필드 변수가 적다면 생성자를 사용해서 객체를 생성하는데 큰 어려움(불편)이 없다.
public class ConstructorTest {
ConstructorEx cx = new ConstructorEx("hello", "this");
}
하지만, 필드에 변수가 엄청나게 많아진다면 어떻게 될까?
public class BuilderEx {
private String Hello;
private String this_;
private String is;
private String an;
private String example;
private String class_;
private String that;
private String was;
private String created;
private String to;
private String implement;
private String builder;
private String patterns;
private String of;
private String builders;
private String If;
private String it;
private String gets;
private String long_;
private String we;
private String have;
private String no;
private String choice;
private String but;
private String use;
private String the;
private String pattern;
public BuilderEx(String hello, String this_, String is, String an, String example, String class_, String that,
String was, String created, String to, String implement...) {
Hello = hello;
this.this_ = this_;
this.is = is;
this.an = an;
this.example = example;
this.class_ = class_;
this.that = that;
this.was = was;
this.created = created;
this.to = to;
this.implement = implement;
...
}
public class BuilderTest {
BuilderEx ex = new BuilderEx("hello", "this", "is", "an", "exmaple",
"class", "that", "was", "created", "to", "implement"...);
몇 번째 순서에 작성해야 어떤 변수에 값이 들어가는지 한눈에 파악하기 어렵고, 필드변수의 수대로 경우의 수 또한 무수하게 늘어날 수밖에 없을 것이다.
2. 일관성이 무너진 Setter를?
매개변수가 없는 생성자를 만든 후에, Setter로 객체에 값을 넣는 방법이 존재한다.
package com.effective.java.builder;
public class BuilderEx {
private String Hello;
private String this_;
private String is;
private String an;
private String example;
private String class_;
private String that;
private String was;
private String created;
private String to;
private String implement;
...
public void setHello(String hello) {
Hello = hello;
}
public void setThis_(String this_) {
this.this_ = this_;
}
public void setIs(String is) {
this.is = is;
}
public void setAn(String an) {
this.an = an;
}
public void setExample(String example) {
this.example = example;
}
public void setClass_(String class_) {
this.class_ = class_;
}
public void setThat(String that) {
this.that = that;
}
public void setWas(String was) {
this.was = was;
}
public void setCreated(String created) {
this.created = created;
}
public void setTo(String to) {
this.to = to;
}
public void setImplement(String implement) {
this.implement = implement;
}
...
}
public class BuilderTest {
public static void main(String[] args) {
BuilderEx ex2 = new BuilderEx();
ex2.setHello("hello");
ex2.setThis_("this");
ex2.setIs("is");
ex2.setAn("an");
ex2.setExample("example");
ex2.setClass_("class");
...
}
코드가 길어진 것 같지만, 생성자보다 명확하게 어떤 변수에 값을 넣을지 한 눈에 알아보기 편해졌다.
여기서 문제는 객체를 생성한 후에 값을 넣게 되므로 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓이게 된다.
또한 객체 하나를 만들기 위해서 많은 메서드를 사용하게 된다.
일관성 또한 무너지게 되므로 클래스를 불변으로 만들수도 없다.
또한 Setter를 사용했을 때, 해당 Setter가 값을 주입하기 위해 만든 메서드인지, 값을 수정하기 위한 메서드인지 의도를 파악하기 어려워진다.
이 두가지 문제점을 해결할 수 있는 것이 바로
Builder 패턴
````
public class BuilderEx {
private String Hello;
private String this_;
private String is;
private String an;
private String example;
private String class_;
private String that;
private String was;
private String created;
private String to;
private String implement;
...
public static class Builder {
// 필수 매개변수
private final String Hello;
private final String this_;
// 선택 매개변수 -> 기본 값으로 초기화
private String is = "default";
private String an = "default";
private String example = "default";
private String class_ = "default";
private String that = "default";
private String was = "default";
private String created = "default";
private String to = "default";
private String implement = "default";
...
public Builder(String Hello, String this_) {
this.Hello = Hello;
this.this_ = this_;
}
public Builder is(String val) {
is = val;
return this;
}
public Builder an(String val) {
an = val;
return this;
}
public Builder example(String val) {
example = val;
return this;
}
public Builder class_(String val) {
class_ = val;
return this;
}
public Builder that(String val) {
that = val;
return this;
}
public Builder was(String val) {
was = val;
return this;
}
public Builder created(String val) {
created = val;
return this;
}
public Builder to(String val) {
to = val;
return this;
}
public Builder implement(String val) {
implement = val;
return this;
}
...
private BuilderEx(Builder builder) {
Hello = builder.Hello;
this_ = builder.this_;
is = builder.is;
an = builder.an;
example = builder.example;
class_ = builder.class_;
that = builder.that;
was = builder.was;
created = builder.created;
to = builder.to;
implement = builder.implement;
...
}
}
위와 같이 빌더를 만들게 되면
public class BuilderTest {
public static void main(String[] args) {
BuilderEx ex3 = new BuilderEx
.Builder("hello", "this")
.is("is")
.an("an")
.example("example")
.class_("class")
.that("that")
.build();
}
}
어떤 변수에 값을 넣는지 한눈에 파악할 수 있고, 한 번에 객체를 생성하게 되므로 일관성 또한 유지하게 된다.
그렇다면 빌더는 완벽한걸까?
그것은 아니다. 빌더 패턴을 만들려면 빌더를 만들어야 하고(Lombok을 사용하면 편하게 만들 수 있으므로 문제 해결), 생성자가 적다면 오히려 빌더를 만드는 게 더 비효율적일 수 있다. 또한 새롭게 빌더를 생성하게 되는 것이므로, 미약하나마 성능에 영향을 끼칠 수도 있다.
결론
매개변수가 많다면, 빌더 패턴을 선택하는 게 높은 확률로 좋다.
Lombok을 사용하면 굳이 빌더를 만들지 않고도 @Builder를 추가함으로서 빌더패턴을 사용할 수 있으니 편하게 사용하자!
'Server > Java' 카테고리의 다른 글
[Java] Javadoc Export 명령어 (0) | 2023.05.16 |
---|---|
[Java] ResponseEntity<> 사용 이유? (1) | 2023.05.15 |
[Spring] Lombok @AllArgsConstructor, @NoArgsConstructor, @RequiredArgsConstructor (0) | 2023.04.26 |
[JDBC] 데이터베이스 연결 (0) | 2022.11.08 |
[JDBC] JDBC와 최신 데이터 접근 기술 (0) | 2022.11.02 |