[디자인 패턴] 빌더 패턴이란?

최동근·2023년 1월 14일
0

디자인패턴

목록 보기
4/4
post-thumbnail

안녕하세요 오늘은 빌더 패턴(Builder Pattern) 에 대해 알아보겠습니다 👨‍💻

👨‍🎨 다양한 객체 생성 방법

기존 객체 생성 방식의 문제점

기존 객체 생성 방식이라 함은 생성자,정적 팩토리 메소드 혹은 자바 빈 패턴 을 사용하는 방식을 의미합니다.
생성자 방식은 new 키워드를 통해 해당 클래스의 필드의 값을 초기화하는 생성 방식이며, 성공적으로 객체 생성시
힙 영역에 객체가 생성되고 객체의 주소가 반환됩니다 🧑🏼‍💻
정적 팩토리 메소드 방식은 해당 클래스의 객체 생성용 정적 메소드를 이용해서 객체를 생성하는 방식입니다.
자바 빈 패턴 은 getter/setter 로 객체를 생성할 때 필드를 주입하는 방식입니다.

그러나 이러한 방법들을 이용한 객체 생성 방법에는 다양한 문제점이 존재합니다 👿

  • 클라이언트 프로그램에서 팩토리 클래스를 호출할 때 Optional한 인자가 많아지면, 타입과 순서에 대한 관리가 어려워져 에러 발생 확률이 높아집니다.

  • 필요 없는 파라미터들에 대해서 팩토리 클래스에 일일이 null 값을 넘겨줘야 합니다.

  • 생성해야 하는 sub class 가 무거워지고 복잡해짐에 따라 팩토리 클래스 또한 복잡해집니다.

  • 자바 빈 패턴 사용시 값이 계속 변화할 수 있기 때문에 객체 일관성이 깨집니다.

실제 어떻게 문제점이 발생하는지 알아보기 위해 코드로 예시를 들어보겠습니다 ⛅️
Item 클래스의 인스턴스를 생성하는 상황인데 weight 필드값은 필요 없다고 가정하겠습니다.

@NoArgsConstructor
@AllArgsConstructor
public class Item {

	private String name;
    private int price;
    private String makerName;
    private Double weight;
    
   
   	public Item(String name, int price, String makerName) {
    
    	this.name = name;
        this.price = price;
        this.makerName = makerName;
        
    } // 생성자를 이용한 객체 생성
    
    
    public static Item of(String name, int price, String makerName) {
    
    	return new Item(name, price, makerName, 0L);
        
    } // 정적 팩토리 메소드를 이용한 객체 생성
    
    public void setName(String name) { 
    	this.name = name;
    } // 자바 빈 패턴
    
    public void setPrice(int price) { 
    	this.price = price;
    } // 자바 빈 패턴
    
    ...// 위와 동일함
    
}

weight 필드가 필요없기 때문에 해당 필드에 더미 값을 넣어주거나 weight가 없는 생성자를 별도로 만들어주어야합니다.
이러한 작업이 쌓이면 매우 번거로워질 수 있습니다 🥲
또한 변경이 필요할 시 유연하지 못한 구조를 가지고 있기 때문에 변경이 힘들 수 있습니다.
이뿐만 아니라, 매개변수가 많아지면 전달하는 순서에서도 오류가 발생할 수 있습니다.

👨‍🎨 빌더 패턴을 사용하자❗️

빌더 패턴은 기존 객체 생성 방식에서 발생하는 문제점을 해결할 수 있는 객체 생성 방식입니다.
또한 자바의 다형성과, 개발자의 편의성을 보장할 수 있습니다.
위에서 제시한 예시 코드에 추가적으로 필드를 붙이고 이를 빌더 패턴으로 구현해보겠습니다.

@NoArgsConstructor
@AllArgsConstructor
@Getter
public class Item {

	private String name;
    private int price;
    private String makerName;
    private Double weight;
    private LocalDateTime createdAt;
    private int amount;
    
    public Item(Builder builder) {
    	name = builder.name;
        price = builder.price;
        makerName = builder.makerName;
        weight = builder.weight;
        createdAt = builder.createdAt;
        amount = builder.amount;
    }
    
    public static class Builder { // Builder inner 클래스
    	private String name = null;
        private int price = 0;
        private String makerName = null;
        private Double weight = null;
        private LocalDateTime createdAt = null;
        private int amount = 0;
    }
    
    public Builder name( String name ) {
    	this.name = name;
        return this;
    }
    
    public Builder price(int price) {
    	this.price = price;
        return this;
    }
        
    public Builder makerName( String makerName ) {
    	this.makerName = makerName;
        return this;
    }
    
    public Builder weight( Double weight ) {
    	this.weight = weight;
        return this;
    }
    
    public Builder createdAt( LocalDateTime createdAt ) {
    	this.createdAt = createdAt;
        return this;
    }
    
    public Builder amount( int amount ) {
    	this.amount = amount;
        return this;
    }
    
    public Item build() { 
    	return new Item(this);
    }
}

Builder 라는 inner 클래스를 지정하고 그 안에서 return this 를 통해 메소드 체이닝을 걸어서 매개변수를 받을 수 있도록 지정하였습니다. 그리고 마지막에 build() 를 통해 객체를 반환하는 방식을 가집니다.
간단한 Test 를 해보겠습니다 👨‍💻

@Test
void BuilderTest () { 

	Item item = new Item.Builder().name("IPhone").price(100000)
    						.makerName("애플").weight(1.2).build();
                            
    assertEquals(item.getName(),"IPhone");
    assertEquals(item.getPrice(),100000);
    assertEquals(item.getMakerName(), "애플");
    assertEqauls(item.getWeight(), 1.2);
}

테스트가 정상적으로 통과되었습니다.
그러나 빌더 패턴이 너무 복잡해보이나요? 맞습니다 필드가 많아지면 inner 클래스를 통해 구현하는게 너무 복잡해지고 귀찮을 수 있습니다.
롬복(Lombok) 라이브러리를 이용하면 빌더 패턴을 매우 효과적으로 구현할 수 있습니다.

@Getter
@Builder // 빌더 패턴을 위한 롬복 어노테이션
public class Item { 

	private String name;
    private int price;
    private String makerName;
    private Double weight;
    private LocalDateTime createdAt;
    private int amount
    
    ...
    
}

해당 코드는 @Builder 를 통해 빌더 패턴을 구현한 코드 예시입니다.
어노테이션 사용을 통해 간단하게 구현 가능하다는 것을 확인 할 수 있습니다 ✅

👨‍🎨 빌더 패턴 장점 정리

이처럼 빌더 패턴은 기존 객체 생성 방식의 단점을 보완할 수 있는 객체 생성 방식입니다.
빌더 패턴 방식은 가장 권장되는 객체 생성 방식인 만큼 다양한 장점이 존재합니다 👍

  • 필요한 데이터만 설정할 수 있습니다.
    매개변수 타입과 개수에 따라서 점층적으로 생성자를 만들 필요 없으며
    필요한 데이터만 가지고 객체를 생성할 수 있습니다.
  • 유연성을 확보할 수 있습니다.
    만약 클래스의 필드를 추가하는 상황에 직면했을 때 빌더 패턴을 사용하면 기존 코드에
    영향을 주지 않으면서 수정할 수 있습니다.
  • 가독성을 높일 수 있습니다.
    매개변수가 많아져도 가독성을 높일 수 있습니다.
    직관적으로 어떤 필드에 어떤 값이 들어가는지 쉽게 파악이 가능합니다.
  • 변경 가능성을 최소화할 수 있습니다.
    Setter (수정자 패턴) 은 불필요하게 변경 가능성을 열어두는 것입니다.
    그래서 보통 클래스의 필드는 불변성을 확보하는 것이 좋습니다.
    이때 final(상수) 를 사용하지 못하는 상황이라면 Setter 를 사용하지 않으면 됩니다.
    빌더 패턴을 사용하면 Setter 를 넣지 않으면서 객체 생성을 용이하게 해줍니다.

이렇게 빌더패턴에 대해 알아보았습니다.
객체를 생성하는 대부분의 경우에는 빌더 패턴을 적용하는 것이 좋습니다.
그러나 해당 객체가 가지고 있는 변수가 2개 이하인 경우에는 굳이 빌더 패턴을 사용하지 않고 정적 팩토리 메소드를 사용하는 것이 더 좋은 경우도 있습니다.
변수의 개수와 변경 가능성 등을 중점적으로 보고 빌더 패턴을 적용할지 판단하면 됩니다🧑🏼‍💻


참고

[Java] 빌더 패턴(Builder Pattern)을 사용해야 하는 이유
[Java-27] 자바 생성자에 대한 고찰
Builder Pattern 빌더 패턴의 특징, 장점 (@Builder 사용이유, @Builder 예제)

profile
비즈니스가치를추구하는개발자

0개의 댓글