Design Pattern

Builder

Jungsoomin :) 2020. 11. 29. 23:57

빌더 패턴도 객체 생성 패턴의 하나이다. 

  • 팩토리 패턴 : 하나의 Factory 메서드로 여러 객체를 만들어낸다. / 객체와 공장의 추상화와 이에 따른 구현클래스를 사용 ( 템플릿메서드와 함께 사용될 수 있다 ) 
  • 싱글톤 패턴 : 하나의 인스턴스 만을 생상하게 만든다.
  • 프로토 타입 패턴 : new 연산자를 사용하지 않고 비용이 높은 ( DB 를 사용하는 등 ) 의 인스턴스 생성과 수정을 가볍게 한다. / 만들 객체의 추상화 과정과 Cloneable 을 이용한 객체 복사가 포인트이다.

빌더 패턴도 새로운 객체를 만드는 패턴이지만, 실제 동작방식은 차례대로 매개변수를 받아 변수를 통합시켜 객체를 만들어내는 방식이다.

사용하는 이유

  1. 복잡한 생성자를 만들어내어 사용자로 하여금 불편함을 주지않기 위함이다.
  2. 요구사항에 따른 다양한 객체생성이 필요할 경우
  3. 생성자의 데이터 입력 순서와 관계없이 객체를 생성하기 위함이다.
  4. 더욱 명시적이며, 확실하게 객체를 생성하기 위함이다.

 


문제점에 대한 이해

기존 생성자를 이용하면, 요구사항에 대한 대처와 다양한 생성요구에 대한 요구를 맞추기가 힘들다.

/*
 * 해당 클래스의 요구 사항은 읽기 전용이다. 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);
    }

빌더 패턴은 이 문제를 어떻게 명시적으로 해결하고 있는가.

  1. 불필요한 생성자를 사용하지 않는다.
  2. 데이터 순서에 관계없이 객체를 생성한다.
  3. 사용자가 볼 때 명시적이고 직관적이이여한다.

빌더 패턴은 이너 클래스 혹은 각자의 클래스로 작성하는 방법이 있다.

 


빌더 클래스의 특징

  • 만들 클래스의 멤버변수를 가지고 있다.
  • 명시적인 메서드로 멤버변수에 값을 대입한다.
  • 각 메서드 들은 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