빌더 패턴도 객체 생성 패턴의 하나이다.
- 팩토리 패턴 : 하나의 Factory 메서드로 여러 객체를 만들어낸다. / 객체와 공장의 추상화와 이에 따른 구현클래스를 사용 ( 템플릿메서드와 함께 사용될 수 있다 )
- 싱글톤 패턴 : 하나의 인스턴스 만을 생상하게 만든다.
- 프로토 타입 패턴 : new 연산자를 사용하지 않고 비용이 높은 ( DB 를 사용하는 등 ) 의 인스턴스 생성과 수정을 가볍게 한다. / 만들 객체의 추상화 과정과 Cloneable 을 이용한 객체 복사가 포인트이다.
빌더 패턴도 새로운 객체를 만드는 패턴이지만, 실제 동작방식은 차례대로 매개변수를 받아 변수를 통합시켜 객체를 만들어내는 방식이다.
사용하는 이유
- 복잡한 생성자를 만들어내어 사용자로 하여금 불편함을 주지않기 위함이다.
- 요구사항에 따른 다양한 객체생성이 필요할 경우
- 생성자의 데이터 입력 순서와 관계없이 객체를 생성하기 위함이다.
- 더욱 명시적이며, 확실하게 객체를 생성하기 위함이다.
문제점에 대한 이해
기존 생성자를 이용하면, 요구사항에 대한 대처와 다양한 생성요구에 대한 요구를 맞추기가 힘들다.
/*
* 해당 클래스의 요구 사항은 읽기 전용이다. Setter 는 작성하지 않는다.
*/
public class Account {
private String username;
private String email;
private String password;
private String address;
public Account(String username, String email, String password, String address) {
this.username = username;
this.email = email;
this.password = password;
this.address = address;
}
public String getUsername() {
return username;
}
public String getEmail() {
return email;
}
public String getPassword() {
return password;
}
public String getAddress() {
return address;
}
public String getAccountInfo(){
return String.format("name: %s , email: %s , password: %s , address : %s", username,email,password,address);
}
}
//
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Account account = new Account("doli0413", "doli061214@gmail.com", "1234", "Tistory");
System.out.println(account.getAccountInfo());
}
}
//
name: doli0413 , email: doli061214@gmail.com , password: 1234 , address : Tistory
여기서 생성자에 대해서 username , password 만 작성하도록 하는 방법도 필요하다. 무조건 부가 정보가 채워져 있지 않을 경우가 많을 것이다.
그렇다면 생성자를 추가로 작성하여 처리한다. 이런 과정을 요구사항에 따라 언제나 만들면 코드는 지저분해 진다.
public class Account {
private String username;
private String email;
private String password;
private String address;
public Account(String username, String email, String password, String address) {
this.username = username;
this.email = email;
this.password = password;
this.address = address;
}
public Account(String username, String password) {
this(username,null,password,null);
}
...
}
또는 명시적으로 null 을 기입하여 기존 생성자를 사용해도 될 것이다. 잘못 적었다간 아주아주 큰일날 것이다.
public static void main(String[] args) throws CloneNotSupportedException {
Account account = new Account("doli0413", "doli061214@gmail.com", "1234", "Tistory");
// 이메일 위치에 패스워드가 작성되어 있다. 재앙이 펼쳐진다.
Account account2 = new Account("doli0413", "1234", null, null);
}
빌더 패턴은 이 문제를 어떻게 명시적으로 해결하고 있는가.
- 불필요한 생성자를 사용하지 않는다.
- 데이터 순서에 관계없이 객체를 생성한다.
- 사용자가 볼 때 명시적이고 직관적이이여한다.
빌더 패턴은 이너 클래스 혹은 각자의 클래스로 작성하는 방법이 있다.
빌더 클래스의 특징
- 만들 클래스의 멤버변수를 가지고 있다.
- 명시적인 메서드로 멤버변수에 값을 대입한다.
- 각 메서드 들은 Builder 클래스 자신의 인스턴스를 리턴한다.
중요한 포인트는 생성한 객체의 멤버변수를 가지고 있으며 이를 이용해서 build() 한다는 것, 모든 Setter 들은 Builder 자신의 인스턴스를 리턴한다는 것 이다. 이 점을 생략하면 메서드 체인이 일어나지 않는다,
public class AccountBuilder {
private String username;
private String password;
private String email;
private String address;
public AccountBuilder setUsername(String username) {
this.username = username;
return this;
}
public AccountBuilder setPassword(String password) {
this.password = password;
return this;
}
public AccountBuilder setEmail(String email) {
this.email = email;
return this;
}
public AccountBuilder setAddress(String address) {
this.address = address;
return this;
}
public Account build() {
return new Account(username, email, password, address);
}
}
//
public class Main {
public static void main(String[] args) {
AccountBuilder accountBuilder = new AccountBuilder();
Account builtAccount = accountBuilder.setUsername("doli0413").setPassword("1234").setEmail("doli061214@gmail.com").build();
System.out.println(builtAccount.getAccountInfo());
}
}
//
name: doli0413 , email: doli061214@gmail.com , password: 1234 , address : null
이너 클래스를 이용하려면 어떻게 하는가.
특징은 다음과 같다.
- 만들 인스턴스를 정의한 클래스의 생성자는 Builder 객체를 받는다.
- static 클래스로 Builder 클래스가 선언된다.
- static 메서드로 Builder 클래스의 인스턴스를 리턴하는 메서드를 만들어주면 좋다.
- 이외는 각자 클래스로 나눈 Builder 패턴 형식과 동일
/*
* 해당 클래스의 요구 사항은 읽기 전용이다. Setter 는 작성하지 않는다.
*/
public class Account {
private String username;
private String email;
private String password;
private String address;
// 생성자는 Builder 클래스를 받도록 한다.
public Account(InnerAccountBuilder accountBuilder) {
this.username = accountBuilder.username;
this.email = accountBuilder.email;
this.password = accountBuilder.password;
this.address = accountBuilder.address;
}
public String getUsername() {
return username;
}
public String getEmail() {
return email;
}
public String getPassword() {
return password;
}
public String getAddress() {
return address;
}
public String getAccountInfo(){
return String.format("name: %s , email: %s , password: %s , address : %s", username,email,password,address);
}
// Static 키워드를 가진 InnerClass 인스턴스 생성 메서드
public static InnerAccountBuilder Builder(){
return new InnerAccountBuilder();
}
//Builder 클래스
public static class InnerAccountBuilder {
private String username;
private String password;
private String email;
private String address;
//메서드체인
public InnerAccountBuilder setUsername(String username) {
this.username = username;
return this;
}
public InnerAccountBuilder setPassword(String password) {
this.password = password;
return this;
}
public InnerAccountBuilder setEmail(String email) {
this.email = email;
return this;
}
public InnerAccountBuilder setAddress(String address) {
this.address = address;
return this;
}
//이너 클래스 필드로 객체를 만드는 build() 메서드
public Account build() {
return new Account(this);
}
}
}
//
public class Main {
public static void main(String[] args) {
Account account = Account.Builder().setUsername("doli0413").setPassword("1234").setEmail("doli061214@gmail.com").setAddress("Tistory").build();
Account account1 = Account.Builder().setUsername("doli0612").setPassword("12356").build();
System.out.println(account.getAccountInfo());
System.out.println(account1.getAccountInfo());
}
}
//
name: doli0413 , email: doli061214@gmail.com , password: 1234 , address : Tistory
name: doli0612 , email: null , password: 12356 , address : null
다시 봐도 재밌다. 빌더클래스를 디자인한 사람은 정말 ...천재같다.
'Design Pattern' 카테고리의 다른 글
Abstract Factory (0) | 2020.11.30 |
---|---|
Prototype - 깊은 복사와 얕은 복사, Cloneable (0) | 2020.11.29 |
Prototype (0) | 2020.11.29 |
SingleTon Pattern (0) | 2020.11.24 |
Factory Method (0) | 2020.11.24 |