ahlight
개발 저장소
ahlight
전체 방문자
오늘
어제
  • 분류 전체보기 (28)
    • Java (5)
    • Spring (6)
    • JPA (2)
    • RDBMS (0)
    • Computer Science (0)
      • 디자인패턴, 프로그래밍 패러다임 (0)
      • 네트워크 (0)
      • 운영체제 (0)
      • 데이터베이스 (0)
    • 알고리즘 (0)
    • 프로그래머스 (0)
    • 백준 (0)
    • 서평 (3)
    • 회고 (1)
    • TIL (0)
    • 기타 (1)

블로그 메뉴

  • 홈

공지사항

인기 글

태그

  • TDD
  • Java
  • 클린코드
  • 넥스트스텝
  • 라즈베리파이4 #홈서버 #포트포워딩 #dhcp

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
ahlight

개발 저장소

Computer Science/디자인패턴, 프로그래밍 패러다임

[디자인 패턴] - Builder Pattern & @Builder

2023. 6. 14. 21:23

Builder Pattern이란?

Creational Pattern(생성 패턴) 중 하나로 복합 객체를 작게 나누어 조립하듯이 객체를 생성하는 패턴이다.

객체의 생성 과정과 표현 방법을 분리하여 같은 프로세를 통해 다른 결과를 만들어 낸다.

 

위 두 문장이 분리된 것처럼 Builder Pattern 또한 구현 방식이 2가지로 나뉜다.

  1. 생성자의 인자가 많아 인자에 어떤 값을 넣을 지 파악하기 어려운 경우(일반적으로 많이 사용, 메소드 체이닝 방식) - 이펙티브 자바의 빌더패턴(객체의 불변성 확보와 생성자의 매개변수가 많은 경우에 유리)
  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

3. https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EB%B9%8C%EB%8D%94Builder-%ED%8C%A8%ED%84%B4-%EB%81%9D%ED%8C%90%EC%99%95-%EC%A0%95%EB%A6%AC#thankYou

    ahlight
    ahlight

    티스토리툴바