[Design Pattern] 빌더 패턴 - 객체 생성 시 불변성, 유연성을 적용하는 기술

·2024년 2월 24일
0

Design Pattern

목록 보기
1/1
post-thumbnail

객체를 생성하는 방법

Setter 기반

필요한 객체를 생성한 후, Setter 메서드를 통해, 생성한 객체의 필드를 할당하는 방식이다.

User user = new User();
user.setName("kim");
user.setAge(23);

Setter 기반 객체 생성의 장점

  • 사용 목적이 명확함 : 각 Setter 메서드 마다, 어떤 속성을 정의하는지 명확하다. set + 필드 (이는 곧, 코드의 가독성을 높인다.)
  • 유연성 : 객체 속성마다 개별적인 설정이 가능하기 때문에, 객체의 생성과 속성 설정에 유연하다.

Setter 기반 객체 생성의 단점

  • 불변성 보장의 어려움: Setter 는 객체의 속성을 직접적으로 변경하는 메서드이므로, 예상치 못한 Setter 메서드 호출로 인해 객체의 불변성을 보장하기 어렵다. 흔히 객체의 불변성을 위해, final 을 적용하기도 하는데, Setter 메서드는 final 과 함께 사용하는 경우 오류가 발생한다.

    불변 객체 : 한 번 생성된 후 내부 상태를 변경할 수 없는 객체. 객체가 변경할 수 없다는 것이 정해져 있으므로, 예측이 가능하고 안정적이기 때문에, 코드의 복잡성을 줄이고, 버그를 방지할 수 있다는 장점이 있다.

  • 일관성 문제 : 객체를 우선적으로 생성한 후, Setter 메서드를 통해 필드에 대한 값을 할당하기 때문에, 객체가 생성된 시점에서는 일관성을 유지했다고 보기 어렵다. 하물며, 만약 개발자가 반드시 적용해야 하는 Setter 메서드 호출을 깜빡하는 경우에는, 자칫 큰 문제로 이어질 수 있다.

    컴파일 단계에서 해당 오류를 잡아주지 않으며, 런타임 환경에서 나타나더라도 원인이 무엇인지 파악하기 어렵다.

  • JPA 사용 지양 : JPA 엔티티의 경우 Setter 메서드 사용을 지양하는데, 값을 생성하는 것인지, 변경하는지 의도를 파악하기 어려우며, Setter 메서드는 기본적으로 public 으로 적용되기 때문에, 의도치 않게 속성 값을 변경할 수 있기 때문이다.

생성자 기반

생성자를 활용하여, 객체 생성 시 필드를 바로 할당하는 방식이다.

User user = new User("kim", 23);

생성자 기반 객체 생성의 장점

  • 불변성 유지 : 생성자 기반 객체 생성은 불변 객체 상태를 만드는 데 유용하다.
  • 일관성 유지 : 객체를 생성한 후 필드별로 값을 할당하는 Setter 기반 객체 생성과는 다르게, 객체를 생성하는 순간 필드의 값을 할당하기 때문에 생성 시점에서 이미 객체의 일관성을 유지한다.
  • 기본값 설정 가능 : 생성자 매개변수에 디폴트 값을 설정하는 것으로, 반드시 해당 매개변수에 대한 값을 넣지 않더라도 필드의 기본값을 설정할 수 있다.

생성자 기반 객체 생성의 단점

  • 의도 파악이 어려움 : 생성자를 통해 객체를 생성하는 경우, 인자로 전달되는 데이터들이 어떤 목적으로 사용되는지 파악하기 어렵다.
  • 유연성 부재 : 생성자의 경우, 클래스의 필드가 변경되는 경우 함께 생성자도 확장, 변경해줘야 한다는 불편함이 있으며, 타입에 의존하는 오버로딩 기본원칙 때문에 필드가 많아질수록, 확장할 수 있는 생성자는 제한적이게 된다.

빌더 패턴

객체를 생성하는 디자인 패턴 중 하나로, 클래스의 일관성을 유지하면서도 필드별 유연한 객체 생성이 가능하여, 실무 협업에서 흔히 사용하는 패턴이다.

내부에 Builder 라는 새로운 static 클래스를 생성한 후, 필드에 필요한 값을 할당할 때 마다 객체 자기 자신을 반환하여, 체이닝을 이루는 것이 빌더 패턴의 특징이라고 할 수 있다.

class User {
	private String name;
	private int age;

	static public class Builder {
		private String name;
		private int age;

		public Builder name(String name) {
			this.name = name;
			return this;
		}

		public Builder age(int age) {
			this.age = age;
			return this;
		}
		
		public User build() {
			return new User(name,age);
		}
	}
}

User user = new User.Builder()
					.name("kim")
					.age(23)
					.build();

빌더 패턴을 활용하면, 각 데이터가 어떤 목적으로 활용되는지 한 눈에 알 수 있고, 객체를 생성할 때를 제외하고는 필드에 대한 업데이트를 원천 봉쇄하기 때문에 불변 객체를 유지할 수 있다. 그리고, 생성하는 객체별로 필요한 값만 설정할 수 있는 유연한 특성을 지니며, build() 를 호출해야만 Builder 클래스에서 해당 객체가 생성되는 것이므로, 클래스의 일관성을 지킬 수 있다.

@Builder

빌더 패턴은 생성자 기반 객체 생성과 Setter 기반 객체 생성의 장점을 모두 살린 유용한 디자인 패턴이지만, 클래스별로 Builder 클래스를 반복적으로 생성하는데는 불편함이 있다.

이를 해결할 방법으로 Lombok@Builder 를 클래스에 적용시키면, 주어진 클래스의 필드를 기반으로 빌더 패턴을 자동으로 생성한다.

@Builder 사용 시 주의 사항

  • @Builder 를 사용하면 불변성을 유지하기 용이하나, 필드에 final 키워드까지 자동으로 적용해주지는 않는다.
  • 클래스에 빌드 메서드와 동일한 이름을 가진 메서드가 이미 존재하는 경우, 충돌이 발생할 수 있다. 이러한 경우, builderMethodName 설정을 통해 이름을 변경하는 것이 가능하다.
  • 기존 객체 생성 대비 성능은 다소 떨어진다. 클래스마다 객체 생성 시 빌더 클래스를 거처셔 구현되기 때문이다. (엄청난 차이는 아니다.)

참고
디자인패턴 빌더 - 곽동운 | 백엔드 데브코스 2기 | 백둥이Deview 220406
[JPA] Entitiy에서 왜 Setter를 사용하지 말라고 할까?
💠 빌더(Builder) 패턴 - 완벽 마스터하기

profile
새로운 것에 관심이 많고, 프로젝트 설계 및 최적화를 좋아합니다.

0개의 댓글