Builder Pattern이란?
Creational Pattern(생성 패턴) 중 하나로 복합 객체를 작게 나누어 조립하듯이 객체를 생성하는 패턴이다.
객체의 생성 과정과 표현 방법을 분리하여 같은 프로세를 통해 다른 결과를 만들어 낸다.
위 두 문장이 분리된 것처럼 Builder Pattern 또한 구현 방식이 2가지로 나뉜다.
- 생성자의 인자가 많아 인자에 어떤 값을 넣을 지 파악하기 어려운 경우(일반적으로 많이 사용, 메소드 체이닝 방식) - 이펙티브 자바의 빌더패턴(객체의 불변성 확보와 생성자의 매개변수가 많은 경우에 유리)
- 객체 생성 시 여러 단계의 순서를 통해 생성을 하고, 해당 순서를 결정한 다음 각 단계를 다양하게 구현하는 방식 (즉, 제품 생산 과정을 정해 두고 여러 재료를 가지고 다양한 제품을 생성하는 것) - GoF의 빌더패턴
예제 코드
첫번째 방식
- Builder를 내부 클래스로 만들어 생성하는 방식(굳이 내부 클래스로 안만들어 상관없음. 대신 Exercise 클래스에 생성자를 별도로 추가해줘서 값을 넣는 방식으로 구현 방법을 변경해야 함)
- 비교적 구현이 간단
- domain 클래스엔 setter가 없기 때문에 불변성 확보 가능
- 메소드 체이닝 방식으로 어떤 값을 넣어야 할지 명확하게 파악할 수 있음
- 메소드 체이닝 방식이기 때문에 멤버별 유효성 체크를 분리할 수 있다.
public class Exercise {
private String name;
private String part;
private int numberOfSet;
private int reps;
private int weight;
static class ExerciseBuilder {
private Exercise exercise;
private String name;
private String part;
private int numberOfSet;
private int reps;
private int weight;
public ExerciseBuilder setName(String name) {
this.name = name;
return this;
}
public ExerciseBuilder setPart(String part) {
this.part = part;
return this;
}
public ExerciseBuilder setNumberOfSet(int numberOfSet) {
this.numberOfSet = numberOfSet;
return this;
}
public ExerciseBuilder setReps(int reps) {
this.reps = reps;
return this;
}
public ExerciseBuilder setWeight(int weight) {
this.weight = weight;
return this;
}
public Exercise build() {
Exercise exercise = new Exercise();
exercise.name = this.name;
exercise.part = this.part;
exercise.numberOfSet = this.numberOfSet;
exercise.reps = this.reps;
exercise.weight = this.weight;
return exercise;
}
}
@Override
public String toString() {
return "Exercise [name=" + name + ", part=" + part + ", numberOfSet=" + numberOfSet + ", reps=" + reps
+ ", weight=" + weight +"]";
}
}
package builderpattern;
public class BuilderTest {
public static void main(String[] args) {
Exercise exercise = new Exercise.ExerciseBuilder()
.setName("벤치 프레스")
.setNumberOfSet(0)
.setPart("가슴")
.setReps(10)
.setWeight(70)
.build();
System.out.println(exercise);
}
}
// 결과 : Exercise [name=벤치 프레스, part=가슴, numberOfSet=0, reps=10, weight=70]
두번째 방식
- 클래스간 의존관계가 생기기 때문에 첫번째 방식에 비해선 비교적 구현이 어려움
- 객체 생성과정을 고정시키고 매개 변수의 값을 다양화하여 여러 종류의 객체를 같은 방식으로 생성할 수 있음
- User(director 역할)클래스를 통해 객체의 생성을 컨트롤하고, 객체 생성 과정이 고정되어 있기 때문에 유지 보수에 용이
<Exercise>
package builderpattern2;
public class Exercise {
private String name;
private String part;
private int numberOfSet;
private int reps;
private int weight;
public void setName(String name) {
this.name = name;
}
public void setPart(String part) {
this.part = part;
}
public void setNumberOfSet(int numberOfSet) {
this.numberOfSet = numberOfSet;
}
public void setReps(int reps) {
this.reps = reps;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Exercise [name=" + name + ", part=" + part + ", numberOfSet=" + numberOfSet + ", reps=" + reps
+ ", weight=" + weight + "]";
}
}
<ExerciseBuilder>
package builderpattern2;
public abstract class ExerciseBuilder {
protected Exercise exercise;
public Exercise getExercise() {
return exercise;
}
public void createExercise() {
exercise = new Exercise();
}
public abstract void buildName();
public abstract void buildPart();
public abstract void buildNumberOfSet();
public abstract void buildReps();
public abstract void buildWeight();
}
<ChestExercise>
package builderpattern2;
public class ChestExercise extends ExerciseBuilder {
@Override
public void buildName() {
exercise.setName("벤치 프레스");
}
@Override
public void buildNumberOfSet() {
exercise.setNumberOfSet(10);
}
@Override
public void buildPart() {
exercise.setPart("가슴");
}
@Override
public void buildReps() {
exercise.setReps(12);
}
@Override
public void buildWeight() {
exercise.setWeight(60);
}
}
<LowerBodyExercise>
package builderpattern2;
public class LowerBodyExercise extends ExerciseBuilder {
@Override
public void buildName() {
exercise.setName("스쿼트");
}
@Override
public void buildNumberOfSet() {
exercise.setNumberOfSet(5);
}
@Override
public void buildPart() {
exercise.setPart("하체");
}
@Override
public void buildReps() {
exercise.setReps(8);
}
@Override
public void buildWeight() {
exercise.setWeight(120);
}
}
<DeltoidsExercise>
package builderpattern2;
public class DeltoidsExercise extends ExerciseBuilder {
@Override
public void buildName() {
exercise.setName("오버헤드 프레스");
}
@Override
public void buildNumberOfSet() {
exercise.setNumberOfSet(8);
}
@Override
public void buildPart() {
exercise.setPart("어깨");
}
@Override
public void buildReps() {
exercise.setReps(15);
}
@Override
public void buildWeight() {
exercise.setWeight(40);
}
}
<User>
package builderpattern2;
public class User {
private ExerciseBuilder exerciseBuilder;
public void setExerciseBuilder(ExerciseBuilder exerciseBuilder) {
this.exerciseBuilder = exerciseBuilder;
}
public Exercise getExercise() {
return exerciseBuilder.getExercise();
}
public void addExercise() {
exerciseBuilder.createExercise();
exerciseBuilder.buildName();
exerciseBuilder.buildNumberOfSet();
exerciseBuilder.buildPart();
exerciseBuilder.buildReps();
exerciseBuilder.buildWeight();
}
}
<BuilderTest>
package builderpattern2;
public class BuilderTest {
public static void main(String[] args) {
User user = new User();
ExerciseBuilder chestExercise = new ChestExercise();
ExerciseBuilder lowerBodyExercise = new LowerBodyExercise();
ExerciseBuilder deltoidsExercise = new DeltoidsExercise();
user.setExerciseBuilder(chestExercise);
user.addExercise();
Exercise chest = user.getExercise();
user.setExerciseBuilder(lowerBodyExercise);
user.addExercise();
Exercise squat = user.getExercise();
user.setExerciseBuilder(deltoidsExercise);
user.addExercise();
Exercise shoulder = user.getExercise();
System.out.println(chest);
System.out.println(squat);
System.out.println(shoulder);
}
}
// 실행 결과
Exercise [name=벤치 프레스, part=가슴, numberOfSet=10, reps=12, weight=60]
Exercise [name=스쿼트, part=하체, numberOfSet=5, reps=8, weight=120]
Exercise [name=오버헤드 프레스, part=어깨, numberOfSet=8, reps=15, weight=40]
@Builder란?
- Lombok을 설치하면 사용 가능한 어노테이션으로 위에서 설명한 방식 중 첫번째 빌더 패턴을 사용할 수 있도록 도와준다.
- 생성자 자체에 @Builder를 붙이거나 class 자체에 붙이는 두 가지 방법으로 사용할 수 있다.
@Builder
public Exercise(String name, String part) {
this.name = name;
this.part = part;
}
@Builder
public class Exercise {
private String name;
private String part;
}
Builder Pattern의 단점
생성자의 매개변수가 적거나 적절하게 활용하지 못할 경우, 두 가지 방식 모두 Builder 클래스를 필요로 하기 때문에 빌더패턴의 남용은 코드 복잡도만 증가 시킨다.
참고 자료 :
1. https://www.youtube.com/watch?v=_GCiJAFU2DU&list=LL&index=3
2. https://ko.wikipedia.org/wiki/%EB%B9%8C%EB%8D%94_%ED%8C%A8%ED%84%B4