티스토리 뷰
● 도입
디자인 패턴의 종류
- 디자인 패턴은 크게 3가지로 나눌 수 있다.
1. 생성 패턴(Creational Patterns) : 오브젝트의 생성에 관련된 패턴
2. 구조 패턴(Structural Patterns) : 상속을 이용해 클래스 / 오브젝트를 조합하여 더 거대한 & 발전된 구조로 만드는 패턴
3. 행동 패턴(Behavioral Patterns) : 필요한 작업을 여러 객체에 분배하여 객체간 결합도를 줄이게 해주는 패턴
Builder Pattern?
- 대표적인 생성 패턴으로써 객체의 생성에 대한 로직과 표현에 대한 로직을 분리해준다.
- 객체의 생성 과정을 유연하게 해준다.
- 객체의 생성 과정을 정의하고 싶거나 객체의 필드가 많아 constructor가 복잡해질 때 유용하다.
● 실습
[기존 User class의 모습]
package com.fastcampus.functionalprogramming.chapter8.model;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
public class User {
private int id;
private String name;
private String emailAddress;
private boolean isVerified;
private LocalDateTime createdAt;
private List<Integer> friendUserIds;
public int getId() {
return id;
}
public User setId(int id) {
this.id = id;
return this;
}
public String getName() {
return name;
}
public User setName(String name) {
this.name = name;
return this;
}
public Optional<String> getEmailAddress() {
return Optional.ofNullable(emailAddress);
}
public User setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
return this;
}
public boolean isVerified() {
return isVerified;
}
public User setVerified(boolean isVerified) {
this.isVerified = isVerified;
return this;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public User setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public List<Integer> getFriendUserIds() {
return friendUserIds;
}
public User setFriendUserIds(List<Integer> friendUserIds) {
this.friendUserIds = friendUserIds;
return this;
}
}
- 지금까지 User class의 instance를 만들때는 아래와 같이 선언을 한 후 setter를 이용해 각 필드(field)들을 set 했다.
User user = new User();
- setter 함수가 존재하면 instance의 필드값이 변경될 가능성이 항상 남아있기에 immutable 하지 못하다고 할 수 있다.
- 이 문제를 해결하려면 setter 함수 자체를 만들지 않으면 된다.
- 그렇다면 setter 함수가 없다면 instance의 필드값을 어떻게 설정할 수 있을까?
- 추가적으로 필드의 수가 많은 class라면 constructor가 비대해지는 문제도 있다.
- 이러한 문제를 해결해주는 것이 바로 Builder Pattern이다.
1.
- Builder Pattern을 만들어보자
- Builder Pattern을 위해서는 User class안에 Builder 라는 'inner class'를 정의해야한다.
package com.fastcampus.functionalprogramming.chapter10.model;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
public class User {
private int id;
private String name;
private String emailAddress;
private boolean isVerified;
private LocalDateTime createdAt;
private List<Integer> friendUserIds;
// User class constructor
// 모든 필드의 값을 Builder에서 가져와 객체를 생성하는 것이다.
public User(Builder builder) {
this.id = builder.id;
this.name = builder.name;
this.emailAddress = builder.emailAddress;
this.isVerified = builder.isVerified;
this.createdAt = builder.createdAt;
this.friendUserIds = builder.friendUserIds;
}
// Builder class의 instance를 만들 수 있게 해주는 역할
// Builder class의 constructor는 외부에서 접근할 수 없으므로(private) 해당 method가 필요하다.
// cf) private는 같은 class 내에서는 접근가능하므로 여기서는 당연히 Builder class constructor에 접근할 수 있다.
public static Builder builder(int id, String name) {
// (id, name)은 Builder constructor가 parameter로 가지고 있으므로 반드시 넣어줘야 한다.
return new Builder(id, name);
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public Optional<String> getEmailAddress() {
return Optional.ofNullable(emailAddress);
}
public boolean isVerified() {
return isVerified;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public List<Integer> getFriendUserIds() {
return friendUserIds;
}
// Builder라는 inner class를 정의
public static class Builder {
// Builder class도 User class와 동일한 field를 갖는다.
private int id;
private String name;
private String emailAddress;
private boolean isVerified;
private LocalDateTime createdAt;
private List<Integer> friendUserIds;
// Builder class의 instance를 만들어 주는 constructor
// 생성 시 id와 name을 필수로 받도록 parameter로 넣었다.
// 결론적으로 User class의 instance를 만들때 id와 name은 필수로 있어야한다는 것
private Builder(int id, String name) {
this.id = id;
this.name = name;
}
// 필수가 아닌 필드를 정의하는 method 1
public Builder withEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
return this;
}
// 필수가 아닌 필드를 정의하는 method 2
public Builder withVerified(boolean isVerified) {
this.isVerified = isVerified;
return this;
}
// 필수가 아닌 필드를 정의하는 method 3
public Builder withCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
// 필수가 아닌 필드를 정의하는 method 4
public Builder withFriendUserIds(List<Integer> friendUserIds) {
this.friendUserIds = friendUserIds;
return this;
}
// Builder class와 User constructor를 연결해주는 역할
public User build() {
// 자기 자신(this)을 념겨 User object를 만드는 것이다.
// User constructor의 parameter가 (Builder builder)이기에 this를 argument로 넣어줘야한다.
return new User(this);
}
}
@Override
public String toString() {
return "User [id=" + id + ", " + (name != null ? "name=" + name + ", " : "")
+ (emailAddress != null ? "emailAddress=" + emailAddress + ", " : "") + "isVerified=" + isVerified
+ ", " + (createdAt != null ? "createdAt=" + createdAt + ", " : "")
+ (friendUserIds != null ? "friendUserIds=" + friendUserIds : "") + "]";
}
}
- 이제 Builder를 이용해 User class의 instance를 만들어보자
package com.fastcampus.functionalprogramming.chapter10;
import com.fastcampus.functionalprogramming.chapter10.model.User;
import com.fastcampus.functionalprogramming.chapter10.model.User.Builder;
public class Chapter10Section1 {
public static void main(String[] args) {
// 첫 줄의 builder method는 static이므로 instance 생성 없이도 호출 가능
User user = User.builder(1, "Alice")
.withEmailAddress("Alice@garden.co.kr")
.withVerified(true)
.build(); // 최종적으로 Builder class의 build method를 호출해 User instance를 반환한다.
System.out.println(user);
}
}
- 아래의 순서를 거쳐서 User class의 instance가 만들어지는 것이다.
[1. User.builder(1, "Alice") 부분]
- static method인 builder method를 호출해 Builder constructor에 값을 넣어준후 return한다.
- Builder class에 있는 Builder constructor는 접근제한자가 private이기에 해당 method를 이용해 Builder constructor에 접근해야한다.
public static Builder builder(int id, String name) {
// (id, name)은 Builder constructor가 parameter로 가지고 있으므로 반드시 넣어줘야 한다.
return new Builder(id, name); // Builder를 return 한다.
}
[2. .withEmailAddress("Alice@garden.co.kr").withVerified(true) 부분]
- 1번에서 Builder를 return 받았으므로 Builder instance가 가지고 있는 setter method를 호출해 필드값을 정의한다.
// 필수가 아닌 필드를 정의하는 method 1
public Builder withEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
return this;
}
// 필수가 아닌 필드를 정의하는 method 2
public Builder withVerified(boolean isVerified) {
this.isVerified = isVerified;
return this;
}
[3. .build(); 부분]
- 최종적으로 반환 받아야하는 값의 형태는 'User'이므로 Builder class와 User constructor를 연결해주는 역할인
build method를 호출한다.
// Builder class 안에 있는 method
public User build() {
// 자기 자신(this)을 념겨 User object를 만드는 것이다.
// User constructor의 parameter가 (Builder builder)이기에 this를 argument로 넣어줘야한다.
return new User(this);
}
// return new User(this); 로 인해 아래 코드가 실행되는 것이다.
public User(Builder builder) {
// builder에서 설정된 필드값을 가져와 설정한다.
this.id = builder.id;
this.name = builder.name;
this.emailAddress = builder.emailAddress;
this.isVerified = builder.isVerified;
this.createdAt = builder.createdAt;
this.friendUserIds = builder.friendUserIds;
}
2.
- 현재 Builder inner class 안에 필드를 설정하는 method가 너무 많다.
→ withEmailAddress, withVerified, withCreatedAt, withFriendUserIds
[현재 User class 안에 있는 Builder class 상태]
public static class Builder {
// Builder class도 User class와 동일한 field를 갖는다.
private int id;
private String name;
private String emailAddress;
private boolean isVerified;
private LocalDateTime createdAt;
private List<Integer> friendUserIds;
// Builder class의 instance를 만들어 주는 constructor
// 생성 시 id와 name을 필수로 받도록 parameter로 넣었다.
// 결론적으로 User class의 instance를 만들때 id와 name은 필수로 있어야한다는 것
private Builder(int id, String name) {
this.id = id;
this.name = name;
}
// 필수가 아닌 필드를 정의하는 method 1
public Builder withEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
return this;
}
// 필수가 아닌 필드를 정의하는 method 2
public Builder withVerified(boolean isVerified) {
this.isVerified = isVerified;
return this;
}
// 필수가 아닌 필드를 정의하는 method 3
public Builder withCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
// 필수가 아닌 필드를 정의하는 method 4
public Builder withFriendUserIds(List<Integer> friendUserIds) {
this.friendUserIds = friendUserIds;
return this;
}
// Builder class와 User constructor를 연결해주는 역할
public User build() {
// 자기 자신(this)을 념겨 User object를 만드는 것이다.
// User constructor의 parameter가 (Builder builder)이기에 this를 argument로 넣어줘야한다.
return new User(this);
}
}
- 필드의 개수가 많아질수록 이러한 method 들의 숫자도 더 많아질 것이고 그러면 Builder class 또한 비대해질 것이다.
- 그렇다면 함수형 프로그래밍을 이용해 하나의 method로 모든 필드의 값을 설정해보자
- Consumer interface를 사용할 것이다.
package com.fastcampus.functionalprogramming.chapter10.model;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
public class User {
private int id;
private String name;
private String emailAddress;
private boolean isVerified;
private LocalDateTime createdAt;
private List<Integer> friendUserIds;
// User class constructor
// 모든 필드의 값을 Builder에서 가져와 객체를 생성하는 것이다.
public User(Builder builder) {
this.id = builder.id;
this.name = builder.name;
this.emailAddress = builder.emailAddress;
this.isVerified = builder.isVerified;
this.createdAt = builder.createdAt;
this.friendUserIds = builder.friendUserIds;
}
// Builder class의 instance를 만들 수 있게 해주는 역할
// Builder class의 constructor는 외부에서 접근할 수 없으므로(private) 해당 method가 필요하다.
// cf) private는 같은 class 내에서는 접근가능하므로 여기서는 당연히 Builder class constructor에 접근할 수 있다.
public static Builder builder(int id, String name) {
// (id, name)은 Builder constructor가 parameter로 가지고 있으므로 반드시 넣어줘야 한다.
return new Builder(id, name);
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public Optional<String> getEmailAddress() {
return Optional.ofNullable(emailAddress);
}
public boolean isVerified() {
return isVerified;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public List<Integer> getFriendUserIds() {
return friendUserIds;
}
// Builder라는 inner class를 정의
public static class Builder {
// 밖에서 접근이 가능해야 하므로 접근제한자를 public으로 변경 by withAllField method
public int id;
public String name;
public String emailAddress;
public boolean isVerified;
public LocalDateTime createdAt;
public List<Integer> friendUserIds;
// Builder class의 instance를 만들어 주는 constructor
// 생성 시 id와 name을 필수로 받도록 parameter로 넣었다.
// 결론적으로 User class의 instance를 만들때 id와 name은 필수로 있어야한다는 것
private Builder(int id, String name) {
this.id = id;
this.name = name;
}
// 단 하나의 setter method(모든 필드의 값을 세팅할 수 있다.)
public Builder withAllField(Consumer<Builder> consumer) {
// consumer 안에서 Builder class의 모든 필드들을 set 할 것이다.
consumer.accept(this);
return this;
}
// Builder class와 User constructor를 연결해주는 역할
public User build() {
// 자기 자신(this)을 념겨 User object를 만드는 것이다.
// User constructor의 parameter가 (Builder builder)이기에 this를 argument로 넣어줘야한다.
return new User(this);
}
}
@Override
public String toString() {
return "User [id=" + id + ", " + (name != null ? "name=" + name + ", " : "")
+ (emailAddress != null ? "emailAddress=" + emailAddress + ", " : "") + "isVerified=" + isVerified
+ ", " + (createdAt != null ? "createdAt=" + createdAt + ", " : "")
+ (friendUserIds != null ? "friendUserIds=" + friendUserIds : "") + "]";
}
}
package com.fastcampus.functionalprogramming.chapter10;
import com.fastcampus.functionalprogramming.chapter10.model.User;
import com.fastcampus.functionalprogramming.chapter10.model.User.Builder;
public class Chapter10Section1 {
public static void main(String[] args) {
User user2 = User.builder(2, "Bob")
.withAllField(builder -> {
builder.emailAddress = "Bob@garden.co.kr";
builder.isVerified = true;
})
.build();
System.out.println(user2);
}
}
'Backend > Java8' 카테고리의 다른 글
#38 함수형 프로그래밍 응용 - Function Composition(함수 합성) (1) | 2022.10.29 |
---|---|
#36 Design Pattern (0) | 2022.10.25 |
#35 함수형 프로그래밍 응용 - Lazy Evaluation(for 최적화) (0) | 2022.10.24 |
#34 함수형 프로그래밍 응용 - Scope & Closure / Curry (0) | 2022.10.23 |
#33 Advanced Stream - Parallel Stream(병렬 Stream) (0) | 2022.10.22 |
- Total
- Today
- Yesterday
- 프로그래머스
- git
- node.js
- 운영체제
- 빅데이터
- Spring Boot
- db
- 빅데이터 분석기사
- MongoDB
- jpa
- spring
- 코딩테스트
- java
- Advanced Stream
- DART
- MySQL
- 코테
- nosql
- 자료구조
- OS
- 알고리즘
- 메모리
- API
- SpringBoot
- Phaser3
- SQL
- Phaser
- Java8
- 프로세스
- Stream
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |