복잡한 객체 생성 과정과 표현 방법을 분리하여 다양한 구성의 인스턴스를 만드는 생성 패턴.
Builder 클래스를 만들어 메소드를 통해 각각 값을 입력받고 build() 메소드로 하나의 인스턴스를 생성하여 리턴하는 패턴
class Student {
private int id;
private String name = "아무개";
private String grade = "freshman";
private String phoneNumber = "010-0000-0000";
public Student(int id, String name, String grade, String phoneNumber) {
this.id = id;
this.name = name;
this.grade = grade;
this.phoneNumber = phoneNumber;
}
@Override
public String toString() {
return "Student { " +
"id='" + id + '\'' +
", name=" + name +
", grade=" + grade +
", phoneNumber=" + phoneNumber +
" }";
}
}
class StudentBuilder {
private int id;
private String name;
private String grade;
private String phoneNumber;
public StudentBuilder id(int id) {
this.id = id;
return this;
}
public StudentBuilder name(String name) {
this.name = name;
return this;
}
public StudentBuilder grade(String grade) {
this.grade = grade;
return this;
}
public StudentBuilder phoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
return this;
}
public Student build() {
return new Student(id, name, grade, phoneNumber); // Student 생성자 호출
}
}
public static void main(String[] args) {
Student student = new StudentBuilder()
.id(2016120091)
.name("임꺽정")
.grade("Senior")
.phoneNumber("010-5555-5555")
.build();
System.out.println(student);
}
- 다른 방식에 비해 한눈에 파악할 수 있다.
- 연속된 동일 타입 매개변수를 많이 설정할 경우 발생할 수 있는 설졍 오류를 방지o
Student student1 =
new StudentBuilder(2016120091) // 필수 멤버
.name("홍길동") // 선택 멤버
.build();
Student student2 =
new StudentBuilder(2016120091) // 필수 멤버
.name("임꺽정") // 선택 멤버
.grade("freshman") // 선택 멤버
.build();
Student student3 =
new StudentBuilder(2016120091) // 필수 멤버
.name("주몽") // 선택 멤버
.grade("Senior") // 선택 멤버
.phoneNumber("010-5555-5555") // 선택 멤버
.build();
class StudentBuilder {
// 초기화 필수 멤버
private int id;
// 초기화 선택적 멤버
private String name;
// 필수 멤버는 빌더의 생성자를 통해 설정
public StudentBuilder(int id) {
this.id = id;
}
// 나머지 선택 멤버는 메서드로 설정
public StudentBuilder name(String name) {
this.name = name;
return this;
}
}
Student student1 =
new StudentBuilder(2016120091) // 필수 멤버
.name("홍길동") // 선택 멤버
.build();
class StudnetBuilder{
...
public StudentBuilder name(String name) {
this.name = name;
return this;
}
public StudentBuilder grade(String grade) {
if (!grade.equals("freshman") && !grade.equals("sophomore") && !grade.equals("junior") && !grade.equals("senior")) {
throw new IllegalArgumentException(grade);
}
this.grade = grade;
return this;
}
...
}
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@ToString
class Person {
private final String name;
private final String age;
private final String gender;
private final String job;
private final String birthday;
private final String address;
}
public static void main(String[] args) {
Person person = Person.builder()
.name("홍길동")
.age("26")
.gender("man") // 선택 파라미터
.job("Warrior")
.birthday("1800.10.10")
.address("조선")
.build();
}
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@ToString
class Person {
private final String name;
private final String age;
private final String gender;
private final String job;
private final String birthday;
private final String address;
// 필수 파라미터 빌더 메서드 구현
public static PersonBuilder builder(String name, String age) {
// 빌더의 파라미터 검증
if(name == null || age == null)
throw new IllegalArgumentException("필수 파라미터 누락");
// 필수 파라미터를 미리 빌드한 빌더 객체를 반환 (지연 빌더 원리)
return new PersonBuilder().name(name).age(age);
}
}
public static void main(String[] args) {
Person person = Person.builder("홍길동", "26") // 필수 파라미터
.gender("man") // 선택 파라미터
.job("Warrior")
.birthday("1800.10.10")
.address("조선")
.build();
System.out.println(person);
}
저자에 따라 2가지로 나뉜다.
class Person {
// final 키워드로 필드들을 불변 객체로 만든다.
private final String name;
private final String age;
private final String gender;
private final String job;
private final String birthday;
private final String address;
// 정적 내부 빌더 클래스
public static class Builder {
// 필수 파라미터
private final String name;
private final String age;
// 선택 파라미터
private String gender;
private String job;
private String birthday;
private String address;
// 필수 파라미터는 빌더 생성자로 받게 한다
public Builder(String name, String age) {
this.name = name;
this.age = age;
}
// 선택 파라미터는 각 메서드를 통해 정의한다
public Builder gender(String gender) {
this.gender = gender;
return this;
}
public Builder job(String job) {
this.job = job;
return this;
}
public Builder birthday(String birthday) {
this.birthday = birthday;
return this;
}
public Builder address(String address) {
this.address = address;
return this;
}
// 대상 객체의 private 생성자를 호출하여 최종 인스턴스화 한다
public Person build() {
return new Person(this); // 빌더 객체 자신을 넘긴다.
}
}
// private 생성자 - 생성자는 외부에서 호출되는것이 아닌 빌더 클래스에서만 호출되기 때문에
private Person(Builder builder) {
this.name = builder.name;
this.age = builder.age;
this.gender = builder.gender;
this.job = builder.gender;
this.birthday = builder.birthday;
this.address = builder.address;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
", gender='" + gender + '\'' +
", job='" + job + '\'' +
", birthday='" + birthday + '\'' +
", address='" + address + '\'' +
'}';
}
}
public static void main(String[] args) {
Person person = new Person
.Builder("홍길동", "26") // static inner class 초기화 (필수 파라미터)
.gender("man") // 선택 파라미터
.job("Warrior")
.birthday("1800.10.10")
.address("조선")
.build();
System.out.println(person);
}

다음 제시된 예제는 data 객체를 빌더를 통해 적절한 문자열로 변환하는 것이다.

- 각 문자열을 알맞은 포맷으로 바꾸는건 director가 맞게 된다.
- 클라이언트는 복잡한 빌드 과정을 알 필요 없이 메소드 호출만으로 결과 도출 o
// 각 문자열 포맷 빌드 과정을 템플릿화 시킨 디렉터
class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
// 일종의 빌드 템플릿 메서드라 보면 된다
public String build() {
StringBuilder sb = new StringBuilder();
// 빌더 구현체에서 정의한 생성 알고리즘이 실행됨
sb.append(builder.head());
sb.append(builder.body());
sb.append(builder.foot());
return sb.toString();
}
}
class Data {
private String name;
private int age;
public Data(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
abstract class Builder {
// 상속한 자식 클래스에서 사용하도록 protected 접근제어자 지정
protected Data data;
public Builder(Data data) {
this.data = data;
}
// Data 객체의 데이터들을 원하는 형태의 문자열 포맷을 해주는 메서드들 (머리 - 중간 - 끝 형식)
public abstract String head();
public abstract String body();
public abstract String foot();
}
// Data 데이터들을 평범한 문자열로 변환해주는 빌더
class PlainTextBuilder extends Builder {
public PlainTextBuilder(Data data) {
super(data);
}
@Override
public String head() {
return "";
}
@Override
public String body() {
StringBuilder sb = new StringBuilder();
sb.append("Name: ");
sb.append(data.getName());
sb.append(", Age: ");
sb.append(data.getAge());
return sb.toString();
}
@Override
public String foot() {
return "";
}
}
public static void main(String[] args) {
// 1. 포맷할 자바 데이터 생성
Data data = new Data("홍길동", 44);
// 2. 일반 텍스트로 포맷하여 출력하기
Builder builder1 = new PlainTextBuilder(data);
Director director1 = new Director(builder1);
String result1 = director1.build();
System.out.println(result1);
}
- 결과 : 일반 문자열로 잘 표현된 것을 볼 수 있다.
-> 디렉터를 통해 일관된 프로세스로 인스턴스를 쉽게 만들 수 있다.
-> 클라이언트 측에선 디렉터가 제공하는 메소드를 호출하기 때문에, 코드를 재사용할 수 있게 된다.