빌더(Builder) 패턴: 생성패턴
복잡한 객체를 생성하는 클래스와 표현하는 클래스를 분리하여, 동일한 절차에서도 서로 다른 표현을 생성하는 방법을 제공한다.
Builder: 객체를 생성하는 데 사용되는 인터페이스
ConcreateBuilder: Builder를 실제로 구현하여 객체를 생성까지 하는 구현 클래스
Director: Builder를 인자로 받아 디테일한 객체를 생성하고 반환해주는 클래스
이 패턴을 사용하면, 동일한 구성코드를 사용하여 다양한 타입과 표현을 제공한다. 결과적으로 생성자를 가독성 좋게 만들어 주는 도구라고 할 수 있다.
생성자나 정적 팩터리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는 것이 더 낫다.
왜 빌더패턴이 필요한가요?
학생이라는 객체가 있다고 해보자. 학생 객체의 생성자에는 매개변수로 이름, 성별, 나이, 학년, 반, 번호가 있다.
Student student = new Student("나자바", "남", 18, 2, 5, 25);
// 성적평균, 과 추가
Student student = new Student("나자바", "남", 18, 2, 5, 25, 85,"이과");
// 나는 전학생, 아직 반이 정해지지 않았어요
Student student = new Student("나자바", "남", 18, null, null, null, null, null);
저 생성자를 처음 봤을 때, 각각의 인자가 구분이 되는가? 매개변수가 늘어남에 따라 가시성이 떨어질 것이다.
또, 경우에 따라서 필요 없는 파라미터들에 대해 일일이 null 값으로 넘겨줘야 한다.
이렇게 객체를 생성할 때, 생성자만 사용할 때 발생할 수 있는 문제를 개선하기 위해 빌더패턴이 고안되었다.
빌더 패턴은 이러한 문제들을 해결하기 위해 별도의 Builder 클래스를 만들어 필수 값에 대해서는 생성자를 통해, 선택적인 값들에 대해서는 메소드를 통해 step-by-step으로 값을 입력받은 후에 build() 메소드를 통해 최종적으로 하나의 인스턴스를 return하는 방식이다.
빌더패턴의 장단점
장점
- 불필요한 생성자를 만들지 않고 객체를 만들 수 있다.
- 데이터의 순서에 상관 없이 객체를 만들어 낸다.
- 가독성이 좋아진다.
- 빌더의 각각의 메서드에서 검증 과정을 분담하는 것이 초기화와 검증을 따로 할 수 있어 유지 보수에 유리하다.
단점
- 클라이언트는 구체적인 인스턴스를 생성하기 전에 반드시 빌더를 생성해야 한다.
- 관리해야 할 클래스가 많아지고 구조가 복잡해지는 단점이 있다.
빌더패턴 예제
Player(InnerClass-Builder)
public class Player {
private String name;
private int HP;
private int MP;
public Player(String name, int HP, int MP){
this.name = name;
this.HP = HP;
this.MP = MP;
}
public static class Builder{
private String name;
private int HP;
private int MP;
public Builder(){
}
public Builder(Player player){
this.name = player.name;
this.HP = player.HP;
this.MP = player.MP;
}
public Builder name(String name){
this.name = name;
return this;
}
public Builder HP(int HP){
this.HP = HP;
return this;
}
public Builder MP(int MP){
this.MP = MP;
return this;
}
public Player build(){
return new Player(name, HP, MP);
}
}
}
클래스 내부에 Builder 정적 클래스를 만들어 코드로 구현할 수 있지만, 롬복이 이 빌더패턴을 제공해준다.
LombokPlayer(Lombok)
import lombok.Builder;
import lombok.Getter;
@Getter @Builder
public class LombokPlayer {
private String name;
private int HP;
private int MP;
}
Client
public class Client {
public static void main(String[] args) {
LombokPlayer lombokPlayer = LombokPlayer.builder()
.name("gkgk")
.MP(500)
.HP(400)
.build();
Player Player = new Player.Builder()
.name("dddd")
.HP(400)
.MP(500)
.build();
LombokPlayer lombokPlayer2 = LombokPlayer.builder()
.HP(400)
.MP(500)
.build();
}
}
InterfaceBuilder
Student(Product)
public class Student {
private long id;
private String name;
private String gender;
private int age;
private int grade;
public Student(long id, String name, String gender, int age, int grade) {
this.id = id;
this.name = name;
this.gender = gender;
this.age = age;
this.grade = grade;
}
@Override
public String toString(){
return "학생정보 id : " + this.id + " 이름: " + this.name + " 성별: " + this.gender + " 나이: " + this.age + " 반: " + this.grade;
}
}
Builder
public interface StudentBuilder {
StudentBuilder name(String name);
StudentBuilder gender(String gender);
StudentBuilder age(int age);
StudentBuilder grade(int grade);
Student build();
}
ConcreteBuilder
public class DefaultStudentBuilder implements StudentBuilder{
private long id;
private String name;
private String gender;
private int age;
private int grade;
@Override
public StudentBuilder name(String name) {
this.name = name;
return this;
}
@Override
public StudentBuilder gender(String gender) {
if(gender.contentEquals("여") || gender.contentEquals("f") || gender.contentEquals("녀")) gender = "여자";
else gender = "남자";
this.gender = gender;
return this;
}
@Override
public StudentBuilder age(int age) {
this.age = age;
return this;
}
@Override
public StudentBuilder grade(int grade) {
if(grade > 10) grade = 0;
this.grade = grade;
return this;
}
public Student build(){
long id = Long.parseLong(String.format("%d%d",this.age,this.grade));
return new Student(id, name, gender, age, grade);
}
}
Clinet
public static void main(String[] args) {
StudentBuilder studentBuilder = new DefaultStudentBuilder();
Student student = studentBuilder.age(17)
.name("김자바")
.gender("녀")
.grade(1)
.build();
System.out.println(student.toString());
}
// 출력
학생정보 id : 171 이름: 김자바 성별: 여자 나이: 17 반: 1
Direct 적용 예제
public interface StudentDirector {
public Student getStudent();
}
public class KimJavaDirector implements StudentDirector{
private StudentBuilder studentBuilder;
public KimJavaDirector(StudentBuilder studentBuilder) {
this.studentBuilder = studentBuilder;
}
@Override
public Student getStudent() {
return studentBuilder.age(19)
.gender("남")
.name("김자바")
.grade(3)
.build();
}
}
public static void main(String[] args) {
StudentBuilder studentBuilder = new DefaultStudentBuilder();
Student student = studentBuilder.age(17)
.name("마라탕")
.gender("녀")
.grade(1)
.build();
System.out.println(student.toString());
StudentDirector kimjavaDirector = new KimJavaDirector(studentBuilder);
Student kimjava = kimjavaDirector.getStudent();
System.out.println(kimjava.toString());
}
//출력
학생정보 id : 171 이름: 마라탕 성별: 여자 나이: 17 반: 1
학생정보 id : 193 이름: 김자바 성별: 남자 나이: 19 반: 3
Reference
'OOP > Design Pattern' 카테고리의 다른 글
06. 싱글톤(Singleton)패턴 (0) | 2023.02.13 |
---|---|
05. 프로토타입(Prototype) 패턴 (0) | 2023.02.13 |
03. 추상 팩토리(Abstract Method) 패턴 (0) | 2023.02.13 |
02. 팩토리 메서드(Factory Method) 패턴 (0) | 2023.02.13 |
00. GoF 디자인 패턴 (0) | 2023.02.13 |