[디자인패턴] 빌더 패턴(Builder Pattern)이란? 빌더 패턴 예제

GoF와 이펙티브 자바의 빌더 패턴

빌더 패턴은 "Design Patterns"의 공동 저자 4명을 부르는 GoF(Gang of Four)에서 소개하는 버전과

이펙티브 자바(Effective Java)에서 소개하는 버전이 존재한다.

 

이 글에서는 요새 자주 쓰이고 Lombok에서도 지원하는 간단한 빌더 패턴에 대해 알아보겠다.

 

참고) GoF 빌더 패턴

 

 

빌더 패턴

/ 디자인 패턴들 / 생성 패턴 빌더 패턴 다음 이름으로도 불립니다: Builder 의도 빌더는 복잡한 객체들을 단계별로 생성할 수 있도록 하는 생성 디자인 패턴입니다. 이 패턴을 사용하면 같은 제작

refactoring.guru

 

빌더 패턴이란?

빌더 패턴이란 복합 객체의 생성 과정과 표현 방법을 분리하여 동일한 생성 절차에서 서로 다른 표현 결과를 만들 수 있게 하는 패턴이다. - 위키백과-

 

빌더 패턴은 복잡한 객체를 생성하는 방법을 정의하는 클래스와 표현하는 클래스를 별도로 분리하여, 동일한 절차에서도 서로 다른 표현을 생성하는 방법을 제공한다.

 

빌더 패턴은 어떤 클래스의 인스턴스를 생성할 때 발생할 수 있는 문제를 개선할 수 있다.

 

예를 들어 다음 코드들을 보자.

 

 

점층적 생성자 패턴

점층적 생성자 패턴은 매개변수를 0개, 1개, 2개... 등 다양한 매개변수를 선택적으로 입력받아 인스턴스를 생성할 때 생성

자를 오버로딩 하는 방식의 패턴이다.

 

class Student{
    private Long id;
    private String name;
    private int age;
    private String email;
    private int classNumber;
    private String teacher;	// 담임선생님
	
    
    // id, name만 필요할 때
    
    public Student(Long id, String name){
    	this.id = id;
        this.name = name;
    }
    
    // 전부 필요할 때
    public Student(Long id, String name, int age, String email, int classNumber, String teacher){
    	this.id = id;
        this.name = name;
        this.age = age;
        this.email = email;
        this.classNumber = classNumber;
        this.teacher = teacher;
   }
}

...
	public static void main(String[] args){
    	Student student1 = new Student(12, "홍길동");
        Student student2 = new Student(14, "아이유", 16, "IU@edam.com", 11, "이지은");
        ...
    }

 

예를 들어 위와 같이 Student라는 클래스의 인스턴스를 생성한다고 하자.

 

이때 id와 name만 필요한 경우와 모든 멤버가 필요한 경우가 있다고 하면, 각각의 생성자를 따로 만들어주어야 한다.

만약 id, name, age 만 필요하다면 또 다른 생성자를 만들어야 하는 불편함이 생긴다.

 

또한, 매개변수의 순서가 중요한 만큼 생성자의 어떤 부분이 무엇인지 파악하기 힘들다.

예를 들어 student2의 생성자만을 보고 14, 16, 11의 차이를 알 수 있는가?

코드를 살펴봐야 14는 id, 16은 age, 11은 classNumber라는 것을 알 수 있다. 

 

따라서 해당 방식은 매개변수를 선택적으로 입력하기가 까다롭다. 물론 모든 멤버를 매개변수로 전달할 때 필요하지 않은 매개변수는 NULL, 0 등으로 보내는 방법도 있다. 

 

예를 들어 위 코드에서 //전부 필요할 때의 생성자만을 두고,

 

new Student(12, "홍길동", 0, NULL, 0, NULL);

 

위와 같이 사용할 수도 있다.

 

하지만 이렇게 하더라도 가독성이 좋지 않고 매개변수의 의미를 파악하기 힘든 것은 마찬가지이다.

 

 

자바 빈(Java Bean) 패턴

점층적 생성자 패턴의 단점을 보완하기 위해 자바 빈 패턴을 사용할 수 있다.

 

class Student{
    private Long id;
    private String name;
    private int age;
    private String email;
    private int classNumber;
    private String teacher;	// 담임선생님
        
    public Student(){}
    
    public void setId(Long id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public void setClassNumber(int classNumber) {
        this.classNumber = classNumber;
    }

    public void setTeacher(String teacher) {
        this.teacher = teacher;
    }
    
}

...
	public static void main(String[] args){
    	Student student1 = new Student();
        student1.setId(12);
        student1.setName("홍길동");
                
        Student student2 = new Student();
        student2.setId(14);
        student2.setName("아이유");
        student2.setAge(16);
        student2.setEmail("IU@edam.com");
        student2.setClassNumber(11);
        student2.setTeacher("이지은");
       
        ...
    }

 

자바 빈 패턴은 기본 생성자로 인스턴스를 생성하고 이후 Setter를 통해 필드값을 설정한다.

 

이렇게 하면 필요한 필드만 값을 초기화할 수 있고 메서드명을 통해 어떤 필드에 값이 들어가는지 확실히 보인다.

 

그러나 자바 빈 패턴은 불변성(immutable) 문제가 발생할 수 있다. Setter를 public으로 열어둔다는 것은, 인스턴스의 상태가 언제든지 변할 수 있다는 것이다. 보통 인스턴스를 생성하고 불변함을 보장해야 하는 경우가 많은데, 자바 빈 패턴을 사용하면 불변성을 보장하지 못하고 외부에 노출돼 변질 가능성이 생겨버린다.

 

 

빌더 패턴

위와 같은 문제들을 해결할 수 있는 방법이 바로 빌더 패턴이다. 특히 이펙티브 자바에서 설명하는 심플 빌더 패턴(Simple Builder Pattern)은 간단하게 빌더 패턴을 구현해 유용하다.

 

심플 빌더 패턴은 구현할 클래스에 정적 내부 클래스(static inner class)로 빌더를 구현해 주면 된다.

class Student{
    private Long id;
    private String name;
    private int age;
    private String email;
    private int classNumber;
    private String teacher;	// 담임선생님
    
    // 빌더 클래스에서만 호출하도록 private 으로 제한
    private Student(Builder builder){
    	this.id = builder.id;
        this.name = builder.name;
        this.age = builder.age;
        this.email = builder.email;
        this.classNumber = builder.classNumber;
        this.teacher = builder.teacher;
    }
    
    public static class Builder{
    	private Long id;
        private String name;
        private int age;
        private String email;
        private int classNumber;
        private String teacher;
        
        public Builder id(Long id){
            this.id = id;
        	return this;
        }
        
        public Builder name(String name){
            this.name = name;
            return this;
        }
        
        public Builder age(int age){
            this.age = age;
            return this;
        }
        
        public Builder email(String email){
            this.email = email;
            return this;
        }
        
        public Builder classNumber(String classNumber){
            this.classNumber = classNumber;
            return this;
        }
        
        public Builder teacher(String teacher){
            this.teacher = teacher;
            return this;
        }
        
        // private 생성자를 호출하고 빌더 객체를 넘겨 인스턴스 반환 
        public Student build(){
            return new Student(this);
        }
        
    }
}

...
	public static void main(String[] args){
    	Student student1 = new Student
                        .Builder()
                        .id(12)
                        .name("홍길동")
                        .bulid();
                
        Student student2 = new Student
                        .Builder()
                        .id(14)
                        .name("아이유")
                        .age(16)
                        .email("IU@edam.com")
                        .classNumber(11)
                        .teacher("이지은")
                        .build();
       
        ...
    }

 

코드를 보면 이해할 수 있지만, 하나하나 설명하자면 다음과 같다.

 

  • 우선 Student의 생성자는 private으로 막아둔다. 오직 빌더 클래스에서만 부를 수 있도록 제한하여 외부의 수정 가능성을 제거한 것이다.
  • 정적 내부 클래스로 Builder 클래스를 만든다. 
  • 파라미터를 각각의 메서드로 받는다.
    • 예를 들어 id는 id메서드를 통해 받는다. setId()나 withId()처럼 명명하는 경우도 있지만 보통 id()와 같이 파라미터와 메서드의 명을 동일하게 하는 경우가 많다.
  • 완성한 빌더 객체를 build() 메서드를 통해 Student 클래스의 private 생성자로 넘긴다.
  • Student의 생성자에서는 빌더 객체를 통해 멤버를 초기화하고 인스턴스를 생성한다.

 

이렇게 빌더 패턴을 사용하면 가독성을 높이고 불변성을 보장할 수 있다.

 

 

빌더 패턴의 장단점

빌더 패턴의 장점은 다음과 같다.

 

  • 어떤 파라미터를 넣는지 보여 가독성을 높일 수 있다.
  • 생성자를 private으로 제한하고 setter를 없애 불변성을 확보할 수 있다.
  • 멤버별 초기화 검증을 분리할 수 있다.
    • 예를 들어 age의 경우 0보다 작은 경우는 있을 수 없다. 이때 age() 메서드에서 이를 따로 검증할 수 있다.

 

빌더 패턴의 단점은 다음과 같다.

 

  • 코드 구현이 복잡해질 수 있다.
  • 생성자보다 성능이 좋지 않다. 생성자보다 과정이 많아져 성능이 나빠질 수 있다.

 

이처럼 빌더 패턴은 장단점이 존재한다. 따라서 무분별한 사용은 금하고 꼭 필요한 상황에만 빌더 패턴을 적용해야 한다. 생성자 매개변수의 수가 너무 많아 파악하기 어렵거나 불변성을 보장해야 하는 경우 등 빌더 패턴의 장점에 맞게 사용해야 한다.

 

실제로 자바에서는 StringBuilder, Stream.Builder API 등 다양한 곳에서 활용되고 있다.

 

Lombok에서 지원하는 @Builder

Lombok에서는 @Builder 어노테이션을 지원한다. 해당 어노테이션을 사용하면 심플 빌더 패턴이 자동으로 적용된다.

 

@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
class Student{
    private Long id;
    private String name;
    private int age;
    private String email;
    private int classNumber;
    private String teacher;	// 담임선생님
    
}

...
	public static void main(String[] args){
    	Student student1 = new Student
                        .Builder()
                        .id(12)
                        .name("홍길동")
                        .bulid();
                
        Student student2 = new Student
                        .Builder()
                        .id(14)
                        .name("아이유")
                        .age(16)
                        .email("IU@edam.com")
                        .classNumber(11)
                        .teacher("이지은")
                        .build();
       
        ...
    }

 

@Builder 어노테이션을 사용하면 내부적으로 빌더 클래스를 생성해 주기 때문에 코드 길이를 확연히 줄일 수 있다.

또한 @AllArgsConstructor 어노테이션에서 AccessLevel을 PRIVATE으로 설정해 두면 모든 멤버를 인자로 갖는 생성자를 private으로 만들어준다. 

 

이 두 개의 어노테이션을 활용하면 간단하게 빌더 패턴을 사용할 수 있어 매우 편리하다.

 

 


참고

 

1. https://refactoring.guru/ko/design-patterns/builder

2. https://ko.wikipedia.org/wiki/%EB%B9%8C%EB%8D%94_%ED%8C%A8%ED%84%B4

3.https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EB%B9%8C%EB%8D%94Builder-%ED%8C%A8%ED%84%B4-%EB%81%9D%ED%8C%90%EC%99%95-%EC%A0%95%EB%A6%AC

4. https://dev-youngjun.tistory.com/197

 

반응형

댓글

Designed by JB FACTORY