안녕하세요 오늘은 빌더 패턴(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
를 통해 빌더 패턴을 구현한 코드 예시입니다.
어노테이션 사용을 통해 간단하게 구현 가능하다는 것을 확인 할 수 있습니다 ✅
이처럼 빌더 패턴은 기존 객체 생성 방식의 단점을 보완할 수 있는 객체 생성 방식입니다.
빌더 패턴 방식은 가장 권장되는 객체 생성 방식인 만큼 다양한 장점이 존재합니다 👍
이렇게 빌더패턴에 대해 알아보았습니다.
객체를 생성하는 대부분의 경우에는 빌더 패턴을 적용하는 것이 좋습니다.
그러나 해당 객체가 가지고 있는 변수가 2개 이하인 경우에는 굳이 빌더 패턴을 사용하지 않고 정적 팩토리 메소드를 사용하는 것이 더 좋은 경우도 있습니다.
변수의 개수와 변경 가능성 등을 중점적으로 보고 빌더 패턴을 적용할지 판단하면 됩니다🧑🏼💻
[Java] 빌더 패턴(Builder Pattern)을 사용해야 하는 이유
[Java-27] 자바 생성자에 대한 고찰
Builder Pattern 빌더 패턴의 특징, 장점 (@Builder 사용이유, @Builder 예제)