[Java] Builder Pattern

brianhwang·2021년 7월 20일
1

Java

목록 보기
2/6
post-thumbnail

Spring Boot를 공부하면서 builder pattern을 처음 접하게 되었다. builder pattern에 대해 알아보기 위해 공부 목적으로 작성합니다.

간단한 builder 코드를 보겠습니다.

Board board = Board.build()
    .title("GoodEvening World")
    .author("Jack")
    .build();

builder pattern에 대해 모르고 위 코드를 봤을 경우에도 대충 어떤 느낌인지 알 수 있습니다. setter 와 비슷한 느낌이라고 할까요? name에 값을 넣어주고, age에 값을 넣어주고... builder pattern 개념에 대해서 알아보죠.

Builder Pattern??

  • 디자인 패턴 중 한나
  • 객체를 생성할 때 흔하게 사용하는 패턴
  • 생성과 표현의 분리

Effective Java

  • 생성자 인자가 많을 경우에는 Builder Patter 적용을 고려
  • 생성자에 매개변수가 많다면 빌더를 고려
  • Effective Java에서 말하는 빌더 패턴은 객체 생성을 깔끔하고 유연하게 하기 위 한 기법.

builder pattern 코드만 봤을 경우 장점과 코드의 효율성을 파악하기 쉽지 않기 때문에 점층적 생성자 패턴과 자바빈 패턴 코드를 같이 보면서 builder pattern 이해를 높여 보겠습니다.


점층적 생성자 패턴(Telescoping Constructor Pattern)

매개변수 개수만큼 생성자를 늘리는 방식이다. 1개의 인자를 받는 생성자도 만들 수 있고, 2개 혹은 전체 인자를 받는 생성자를 만들어도 된다.

  1. 필수 인자를 받는 필수 생성자를 하나 만든다.
  2. 1 개의 선택적 인자를 받는 생성자를 추가한다.
  3. 2 개의 선택적 인자를 받는 생성자를 추가한다.
  4. 반복 ...
  5. 모든 선택적 인자를 다 받는 생성자를 추가한다.
// 점층적 생성자 패턴 코드의 예 : 회원가입 관련 코드
public class Member {

    private final String name;      // 필수 인자
    private final String location;  // 선택적 인자
    private final String hobby;     // 선택적 인자

    // 필수 생성자
    public Member(String name) {
        this(name, "출신지역 비공개", "취미 비공개");
    }

    // 1 개의 선택적 인자를 받는 생성자
    public Member(String name, String location) {
        this(name, location, "취미 비공개");
    }

    // 모든 선택적 인자를 다 받는 생성자
    public Member(String name, String location, String hobby) {
        this.name = name;
        this.location = location;
        this.hobby = hobby;
    }
}

장점

new Member("Manning","LA","football") 같은 호출이 빈번하게 일어난다면, new Member("Manning") 으로 대체 할 수 있다.

단점

  • 다른 생성자를 호출하는 생성자가 많아져서, 인자가 추가되는 일이 발생하면 코드를 수정하기 어렵다.
  • 인자 수가 많을 경우 호출 코드만 봐서는 의미를 알기 어렵기 때문에 코드 가독성이 떨어진다. (아래 코드만 봐서는 인자 값이 무엇을 의미 하는지 파악하기 어렵다)
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 3, 35, 27);
NutritionFacts pepsiCola = new NutritionFacts(220, 10, 110, 4, 30);
NutritionFacts mountainDew = new NutritionFacts(230, 10);

자바빈 패턴

점층적 생성자 패턴의 인자 수가 늘어나면 코드를 파악하는게 어려웠다. 이를 해결하기 위한 방법 중 하나로 자바빈 패턴을 쓴다.
자바빈 패턴은 생성자에 전달되는 인자 수가 많을 때 적용 한다. setter 메소드를 이용하는 것이다.

public class CafeMenu {
	private int coffee = 1;		//필수 
	private int beverage = 1;	//필수 
	private int dessert = 0;	//선택 
	private int bakery = 0;		//선택
	private int drinks = 0;		//선택 

	public CafeMenu(){}

	//setter 
	public void setCoffee(int coffee) {
		this.coffee = coffee;
	}

	public void setBeverage(int beverage) {
		this.beverage = beverage;
	}

	public void setDessert(int dessert) {
		this.dessert = dessert;
	}

	public void setBakery(int bakery) {
		this.bakery = bakery;
	}

	public void setDrinks(int drinks) {
		this.drinks = drinks;
	}
}


CafeMenu starbucks = new CafeMenu();
		starbucks.setCoffee(10);
		starbucks.setBeverage(30);
		starbucks.setDessert(10);
		starbucks.setBakery(20);
		starbucks.setDrinks(5);

학원 교육을 받았을때 가장 많이쓰고 이거 밖에 몰랐는데 이게 자바빈 패턴이였는지도 모르고 쓰고 있었다.

장점

  • 각 이자 의미를 파악하기 쉽다.
  • 복잡하게 여러 개의 생성자를 만들지 않아도 된다.

단점

  • 객체 일관성(consistency)이 깨진다.
    - 1회의 함수 호출로 객체 생성이 끝나지 않는다.
  • setter 메소드가 있을므로 변경 불가능(immutable) 클래스를 만들 수 없다.
  • 스레드 안정성을 확보하려면 점층적 생성자 패턴보다 많은 일을 해야 한다.

Build Pattern

이제 Builder Pattern을 코드로 보고 어떤 장점과 단점이 있는지 알아보자.

public class CafeMenu {
    private final int coffee;
    private final int beverage;
    private final int dessert;
    private final int bakery;
    private final int drinks;

    public static class Builder {
        // Required parameters(필수 인자)
        private final int coffee;
        private final int beverage;

        // Optional parameters - initialized to default values(선택적 인자는 기본값으로 초기화)
        private int dessert      = 0;
        private int bakery           = 0;
        private int drinks  = 0;

        public Builder(int cofee, int beverage) {
            this.coffee = coffee;
            this.beverage    = beverage;
        }

        public Builder dessert(int val) {
            dessert = val;
            return this;    // 이렇게 하면 . 으로 체인을 이어갈 수 있다.
        }
        public Builder bakery(int val) {
            bakery = val;
            return this;
        }
        public Builder drinks(int val) {
            drinks = val;
            return this;
        }
        public NutritionFacts build() {
            return new CafeMenu(this);
        }
    }

    private CafeMenu(Builder builder) {
        coffee  	 = builder.coffee;
        beverage     = builder.beverage;
        dessert      = builder.dessert;
        bakery       = builder.bakery;
        drinks       = builder.drinks;

    }
}

위와 같이 만들어주면 다음과 같이 객체를 생성할 수 있다.

CafeMenu.Builder builder = new CafeMenu.Builder(2 , 2);
builder.dessert(1);
builder.bakery(1);
builder.drinks(2);
CafeMenu menu1 = builder.build(); 

혹은 아래와 같이 체인도 가능하다.

CafeMenu menu1 = new CafeMenu
	.Builder(2,2)
    .dessert(1)
    .baker(1)
    .drink(2)
    .build(); //build()가 객체를 생성해 돌려준다. 

장점

  • 각 인자가 어떤 의미인지 알기 쉽다.
  • setter 메소드가 없으므로 변경 불가능 객체를 만들 수 있다.
  • 한 번에 객체를 생성하므로 객체 일관성이 깨지지 않는다.
  • build() 함수가 잘못된 값이 입력되었는지 검증하게 할 수도 있다.

reference: 🧐

profile
Jiujitsu_coder

0개의 댓글