Builder Pattern

반영환·2023년 5월 24일
0

스프링 이모저모

목록 보기
3/12
post-thumbnail

Builder Pattern

Why Builder?

  1. 필요한 데이터만 설정할 수 있음.
  2. 유연성을 확보할 수 있음.
  3. 가독성을 높일 수 있음.
  4. 불변성을 확보할 수 있음.

Builder Pattern?

  • 복합 객체의 생성 과정표현 방법을 분리해서 동일한 생성 절차에서 서로 다른 표현 결과를 만들 수 있게 한다.

preview

@Data
@Builder(builderMethodName = "AlsoProblemBuilder")
public class AlsoProblem extends BaseTimeEntity {

    private Long problemId;

    private AlsoCounter alsoCounter;

    private String title;

    private String content;

    private String category;

    private int importanceCount;

    private String email;

    private String phone;

	public static AlsoProblemBuilder builder(String title) {
		if(title == null) {
			throw new IllegalArgumentException("필수 파라미터 누락");
		}
		this.title = title;
	}
    
    public AlsoProblemDto toDto() {
        return AlsoProblemDto.builder()
                .title(title)
                .content(content)
                .category(category)
                .importanceCount(importanceCount)
                .email(email)
                .phone(phone)
                .counterId(alsoCounter.getCounterId()).build();
    }

    public void setAlsoCounter(AlsoCounter alsoCounter) { this.alsoCounter = alsoCounter ;}
}

No Lombok

Lombok : 프로젝트 롬복은 표준판 코드를 최소화하거나 제거하는 데 사용되는 인기 있고 널리 사용되는 자바 라이브러리입니다. 개발 시간과 노력을 절약해주며, annotation(어노테이션, @)을 사용하여 소스 코드 가독성을 증가시켜줍니다.

build.gradle

dependencies {
	compileOnly 'org.projectlombok:lombok:1.18.24'
	annotationProcessor 'org.projectlombok:lombok:1.18.24'

	testCompileOnly 'org.projectlombok:lombok:1.18.24'
	testAnnotationProcessor 'org.projectlombok:lombok:1.18.24'
}

빌더 패턴으로 생성자 Class 생성

public final class Hero {
	private final Profession profession;
	private final String name;
	private final HairType hairType;
	private final HairColor hairColor;
	private final Armor armor;
	private final Weapon weapon;

	private Hero(Builder builder) {
		this.profession = builder.profession;
		this.name = builder.name;
		this.hairColor = builder.hairColor;
		this.hairType = builder.hairType;
		this.weapon = builder.weapon;
		this.armor = builder.armor;
	}
}

빌더 Class 생성

public static class Builder {
	private final Profession profession;
	private final String name;
	private HairType hairType;
	private HairColor hairColor;
	private Armor armor;
	private Weapon weapon;

	public Builder(Profession profession, String name) {
		if (profession == null || name == null) {
			throw new IllegalArgumentException("profession and name can not be null");
		}
		this.profession = profession;
		this.name = name;
	}

	public Builder withHairType(HairType hairType) {
		this.hairType = hairType;
		return this;
	}

	public Builder withHairColor(HairColor hairColor) {
		this.hairColor = hairColor;
		return this;
	}
    
	public Builder withArmor(Armor armor) {
		this.armor = armor;
		return this;
	}

	public Builder withWeapon(Weapon weapon) {
		this.weapon = weapon;
		return this;
	}

	public Hero build() {
		return new Hero(this);
	}
}

빌더 패턴 사용

Hero mage = new Hero
	.Builder(Profession.MAGE, "Riobard")
	.withHairColor(HairColor.BLACK)
	.withWeapon(Weapon.DAGGER)
	.build();

With Lombok

Lombok 없이 사용하기엔 코드가 너무 길고 복잡해진다. 따라서 @Builder 를 사용하면 따로 빌더 클래스를 작성할 필요가 없다.

@Builder need @AllArgsConstructor

빌더 패턴 적용 클래스

@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder(builderMethodName = "HeroBuilder")
@ToString
public class Hero {

	private final Profession profession;
	private final String name;
	private final HairType hairType;
	private final HairColor hairColor;
	private final Armor armor;
	private final Weapon weapon;

	public static HeroBuilder builder(String name) {
		if(name == null) {
			throw new IllegalArgumentException("필수 파라미터 누락");
		}
		this.name = name;
	}
}

클래스 생성

Hero hero = Hero.builder("아이언맨")
	.profession(Profession.MAGE, "Riobard")
	.hairType("Paris flight ticket")
	.hairColor(HairColor.BLACK)
	.armor("1235-5345")
	.weapon(Weapon.DAGGER)
	.build();
  • @AllArgsConstructor(access = AccessLevel.PRIVATE)
    - @Builder 어노테이션을 선언하면 전체 인자를 갖는 생성자를 자동으로 만든다.
    - @AllArgsConstructor는 전체 인자를 갖는 생성자를 만드는데, 접근자를 private으로 만들어서 외부에서 접근할 수 없도록 만든다.
    • Null 데이터를 허용하지 않음! 모든 파라미터에 대해서 요구를 하기 때문
  • @Builder
    - 위에서 설명한 듯이 Builder 패턴을 자동으로 생성해주는데, builderMethodName에 들어간 이름으로 빌더 메소드를 생성해준다.
  • 클래스 내부 builder 메소드
    - 필수로 들어가야 할 필드들을 검증하기 위해 사용되며, 꼭 name이 아니더라도 해당 클래스를 객체로 생성할 때 필수적인 필드가 있다면 활용할 수 있다. PK를 보통 지정한다.

NoArgsConstructor / AllArgsConstructor

https://erjuer.tistory.com/106

@Builder 작동 원리

https://velog.io/@park2348190/Lombok-Builder의-동작-원리

클래스 레벨

If a class is annotated, then a private constructor is generated with all fields as arguments (as if @AllArgsConstructor(access = AccessLevel.PRIVATE) is present on the class), and it is as if this constructor has been annotated with @Builder instead.

필드의 모든 값에 대한 빌드 메서드가 적용된다.

단, final의 경우 초기화 되지 않은 경우만! 이미 초기화가 됐다면 build 패턴으로 재선언할 수 없다.

Note that this constructor is only generated if you haven't written any constructors and also haven't added any explicit @XArgsConstructor annotations. In those cases, lombok will assume an all-args constructor is present and generate code that uses it; this means you'd get a compiler error if this constructor is not present.

그니까 생성자를 @Builder 어노테이션을 사용했을 땐 임의로 명시하지 말아라. 라는 뜻.
컴파일 과정에서 에러가 난다.

생성자 레벨

The effect of @Builder is that an inner class is generated named TBuilder, with a private constructor. Instances of TBuilder are made with the method named builder() which is also generated for you in the class itself (not in the builder class).

The TBuilder class contains 1 method for each parameter of the annotated constructor / method (each field, when annotating a class), which returns the builder itself.

즉, 생성자 레벨에서는 내부 클래스를 private 생성자로 만들어 builder를 구현하고 생성자의 파라미터에 해당하는 내용만 메서드로 만들어준다.

@SuperBuilder

해당 객체가 부모 객체를 상속받는 상태일 때, 부모 객체의 필드 값 또한 builder 패턴으로 작성하고 싶을 때 @Builder대신 사용한다.

profile
최고의 오늘을 꿈꾸는 개발자

0개의 댓글