μ—…λ°μ΄νŠΈ:

πŸ“Œ λͺ¨λ“  μ†ŒμŠ€λŠ” Github에 μžˆμŠ΅λ‹ˆλ‹€.

Effective Javaμ—μ„œ λ°°μ› λ˜ Build νŒ¨ν„΄μ„ μ •λ¦¬ν•œ 포슀트 μž…λ‹ˆλ‹€.

Builder νŒ¨ν„΄μ΄λž€?

객체λ₯Ό μƒμ„±ν•˜κΈ° μœ„ν•΄ λ‹€μˆ˜μ˜ Parameterκ°€ ν•„μš”ν• λ•Œ Setterλ₯Ό μ“°λŠ” 것 말고 λ©”μ†Œλ“œ 체이닝 λ°©μ‹μœΌλ‘œ 객체λ₯Ό μƒμ„±ν•˜λŠ” 방식을 λ§ν•©λ‹ˆλ‹€.

πŸ’‘λ©”μ†Œλ“œ μ²΄μ΄λ‹μ΄λž€? μ—¬λŸ¬ λ©”μ†Œλ“œλ₯Ό μ΄μ–΄μ„œ ν˜ΈμΆœν•˜λŠ” 방식을 λ§ν•˜λ©°, λ©”μ†Œλ“œ λ§ˆλ‹€ thisλ₯Ό λ°˜ν™˜ν•˜μ—¬ 순차적으둜 ν˜ΈμΆœν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

λ‹€μ–‘ν•œ λͺ©μ μ΄ μžˆκ² μ§€λ§Œ 객체λ₯Ό 생성 μ‹œ Setter와 달리 λΆˆλ³€ 객체λ₯Ό μƒμ„±ν•˜κΈ° μœ„ν•΄ μ‚¬μš©ν•©λ‹ˆλ‹€.

πŸ’‘λΆˆλ³€κ°μ²΄λž€? 객체 생성 이후 λ‚΄λΆ€μ˜ μƒνƒœκ°€ λ³€ν•˜μ§€ μ•ŠλŠ” 객체둜 μƒμ„Έν•œ λ‚΄μš©μ€ λ‹€λ₯Έ ν¬μŠ€νŒ…μ— μ˜¬λ¦¬κ² μŠ΅λ‹ˆλ‹€~

직접 μž‘μ„±ν•œ Builder μ½”λ“œ


Post (μ›λž˜ 객체)

λ‹€μŒμ€ Post 객체 μž…λ‹ˆλ‹€.

public class Post {

    @Id
    private Long id;

    @Column(length = 255, nullable = false)
    private String title;

    @Column(nullable = false)
    private String content;

    private String author;

    // (1)
    public static PostBuilder builder(String title, String content) {
        return new PostBuilder(title, content);
    }

}

(1) PostBuilderλ₯Ό μƒμ„±ν•˜κΈ° μœ„ν•œ λ©”μ†Œλ“œ builder()λ₯Ό μž‘μ„±ν–ˆμŠ΅λ‹ˆλ‹€.

builder() λ©”μ†Œλ“œλŠ” static으둜 μ„ μ–Έν•˜κ³ , ν•„μˆ˜λ‘œ μ„ΈνŒ…ν•  ν•„λ“œλ₯Ό 인자둜 ν•˜μ—¬ λ©”μ†Œλ“œλ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.

Builder 클래슀

객체 μ•ˆμ— Inner Class둜 Builder 클래슀λ₯Ό μ„ μ–Έν•©λ‹ˆλ‹€.

Inner ClassλŠ” λ‹€μŒκ³Ό 같이 μƒκ²ΌμŠ΅λ‹ˆλ‹€.

// (1)
public static class PostBuilder {

    // (2)
    private String title;
    private String content;
    private String author;

    // (3)
    public PostBuilder(String title, String content) {
        this.title = title;
        this.content = content;
    }

    // (4)
    public PostBuilder author(String author) {
        this.author = author;
        return this;
    }

    // (5)
    public Post build() {
        return new Post(this.title, this.content, this.author);
    }

}

(1) Builder ν΄λž˜μŠ€λŠ” static으둜 μ„ μ–Έν•©λ‹ˆλ‹€.

(2) Inner Class에 멀버 ν•„λ“œλŠ” μ›λž˜ 객체, 즉, Post와 λ™μΌν•˜κ²Œ μž‘μ„±ν•©λ‹ˆλ‹€.

πŸ’‘κΌ­ λ™μΌν•œ ν•„μš”λŠ” μ—†μŠ΅λ‹ˆλ‹€. Post 객체λ₯Ό Builder둜 생성 μ‹œ ν•„μš”ν•œ ν•„λ“œλ§Œ 적으면 λ˜μ§€λ§Œ λŒ€λΆ€λΆ„ λͺ¨λ“  ν•„λ“œλ₯Ό κΈ°μž¬ν•©λ‹ˆλ‹€.

(3) ν•„μˆ˜λ‘œ μ„ΈνŒ…ν•  ν•„λ“œλ₯Ό 인자둜 ν•˜λŠ” PostBuilder μƒμ„±μžλ₯Ό λ§Œλ“­λ‹ˆλ‹€.

(4) μΆ”κ°€λ‘œ μ„ΈνŒ…ν•  ν•„λ“œ λ©”μ†Œλ“œ, author()λ₯Ό μž‘μ„±ν•©λ‹ˆλ‹€.

μ—¬κΈ°μ„œ λ©”μ†Œλ“œ 체이닝이 μΌμ–΄λ‚©λ‹ˆλ‹€.

(5) 객체, Postλ₯Ό λ°˜ν™˜ν•  build() λ©”μ†Œλ“œλ₯Ό μž‘μ„±ν•©λ‹ˆλ‹€.

μ‚¬μš©λ²•

μœ„μ˜ μ½”λ“œλ₯Ό 보면 μ§μž‘μ΄ κ°€μ‹œκ² μ§€λ§Œ Buliderλ₯Ό 생성 μ‹œ ν•„μˆ˜ 값을 λ„˜κΈ°κ³ , λ©”μ†Œλ“œ 체이닝 λ°©μ‹μœΌλ‘œ authorλ₯Ό μ„ΈνŒ…ν•©λ‹ˆλ‹€.

PostBuilder builder = Post.builder("title", "content");
builder.author("author");
Post build = builder.build();

Builderλ₯Ό 직접 μž‘μ„±ν•˜λŠ” 방법에 λŒ€ν•΄μ„  μ•Œμ•„λ΄€μŠ΅λ‹ˆλ‹€.

ν•˜μ§€λ§Œ μš°λ¦¬λŠ” κ°“bok, Lombok을 μ‚¬μš©ν•˜λ©΄ Builder νŒ¨ν„΄μ„ μ’€ 더 μ‰½κ²Œ μ΄μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

πŸ’‘κ·Έλž˜λ„ Builder νŒ¨ν„΄μ„ 직접 ν•œλ²ˆ μ§œλ³΄μ‹œλŠ”κ±Έ μΆ”μ²œλ“œλ¦½λ‹ˆλ‹€. Builderκ°€ μ–΄λ–€ μ‹μœΌλ‘œ λ™μž‘ν•˜λŠ”μ§€λ₯Ό μ•Œλ©΄ 도움이 많이 λ˜μ‹€κ²λ‹ˆλ‹€~

Lombok을 μ‚¬μš©ν•œ Builder


μš°μ„  @Builder μ–΄λ…Έν…Œμ΄μ…˜μ˜ λ‚΄λΆ€λ₯Ό ν•œλ²ˆ λ³΄κ² μŠ΅λ‹ˆλ‹€.

@Target({TYPE, METHOD, CONSTRUCTOR})
@Retention(SOURCE)
public @interface Builder {

	@Target(FIELD)
	@Retention(SOURCE)
	public @interface Default {}

	String builderMethodName() default "builder";
	
	String buildMethodName() default "build";

	String builderClassName() default "";

	boolean toBuilder() default false;

	AccessLevel access() default lombok.AccessLevel.PUBLIC;

	....
}

@Builder μ–΄λ…Έν…Œμ΄μ…˜μ€ 클래슀, μΈν„°νŽ˜μ΄μŠ€, enumκ³Ό λ©”μ†Œλ“œ, μƒμ„±μžμ— μ„ μ–Έν•  수 μžˆμŠ΅λ‹ˆλ‹€.

ν΄λž˜μŠ€μ— μ„ μ–Έν•  경우 λͺ¨λ“  ν•„λ“œλ₯Ό λ©”μ†Œλ“œ μ²΄μ΄λ‹μœΌλ‘œ ν•˜λŠ” μ—°κ²°ν•˜λŠ” Builderκ°€ λ§Œλ“€μ–΄μ§‘λ‹ˆλ‹€.

λ©”μ†Œλ“œμ— μ„ μ–Έν•  경우 λ©”μ†Œλ“œμ˜ μΈμžκ°’λ§Œ λ©”μ†Œλ“œ μ²΄μ΄λ‹μœΌλ‘œ μ—°κ²°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

// ν΄λž˜μŠ€μ— @Builder μ„ μ–Έ
@Builder
public class Post {

    @Id
    private Long id;

    @Column(length = 255, nullable = false)
    private String title;

    @Column(nullable = false)
    private String content;

    private String author;
		
}

// μ‚¬μš©
public void lombokBuilderPattern() {
    Post post = Post.builder()
  .id(1L)
  .title("title")
  .content("content")
  .author("author")
  .build();
}
// λ©”μ†Œλ“œμ— @Builder μ„ μ–Έ
@Builder
public Post(String title, String content) {
		this.title = title;
		this.content = content;
}

// μ‚¬μš©
public void lombokBuilderPattern() {
    Post post = Post.builder()
  .title("title")
  .content("conent")
  .build();
}

@Builder의 μ—¬λŸ¬ 속성듀

@Builder μ–΄λ…Έν…Œμ΄μ…˜μ—λŠ” μ—¬λŸ¬κ°€μ§€ 속성 듀이 μžˆλŠ”λ° ν•˜λ‚˜ μ”© 보도둝 ν•˜κ² μŠ΅λ‹ˆλ‹€.

  1. @DefaultλΌλŠ” μ–΄λ…Έν…Œμ΄μ…˜μ΄ ν•˜λ‚˜ 더 μžˆλŠ”λ°, μ΄λŠ” Builder μ‚¬μš© μ‹œ μ΄ˆκΈ°ν™”ν•˜μ§€ μ•ŠμœΌλ©΄, λ³€μˆ˜μ˜ κΈ°λ³Έ 값을 μ§€μ •ν•©λ‹ˆλ‹€. λ‹€μŒκ³Ό 같이 μ„ μ–Έν•˜λ©΄ β€œdefault author”가 λ“€μ–΄κ°€λŠ” 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

     @Builder.Default
     private String author = "default author";
        
     // μ‚¬μš©
     Post post = Post.builder()
       .title("title")
       .content("conent")
       .build();
    

    builder_default

  2. builderMethodName은 builderClassName은 ν•œ 클래슀 μ•ˆμ—μ„œ @Builderλ₯Ό 2개 μ΄μƒμ˜ μƒμ„±μžμ—μ„œ μ‚¬μš©ν•  λ•Œ 같이 μ‚¬μš©ν•©λ‹ˆλ‹€.

    πŸ’‘νŠΉνžˆ μƒμ„±μžμ— μΈμžκ°’κ³Ό λ™μΌν•˜κ²Œ λ©”μ†Œλ“œ 체이닝을 ν•˜κ³ μž ν•œλ‹€λ©΄ builderMethodName은 builderClassName두 개λ₯Ό ν•¨κ»˜ μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.

     @Builder(builderClassName = "PostTitleBuilder", builderMethodName = "postTitleBuilder")
     public Post(String title) {
         this.title = title;
     }
        
     @Builder(builderClassName = "PostTitleAndContentBuilder", builderMethodName = "postTitleAndContentBuilder")
     public Post(String title, String content) {
         this.title = title;
         this.content = content;
     }
    
     Post post1 = Post.postTitleBuilder()
       .title("title")
       .build();
        
     Post post2 = Post.postTitleAndContentBuilder()
       .title("title")
       .content("content")
       .build();
    

    λ§Œμ•½ μœ„μ—μ„œ builderClassNameλ₯Ό μƒλž΅ν•˜κ³  builderMethodName만 μ‚¬μš©ν•œλ‹€λ©΄ postTitleBuilder() λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν• λ•Œλ„, content()κ°€ μ‚¬μš© κ°€λŠ₯ν•œ 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

  3. toBuilderλŠ” builderλ₯Ό μ΄μš©ν•˜μ—¬ μƒμ„±λœ κ°μ²΄μ—μ„œ νŠΉμ • κ°’λ§Œ λ³€κ²½ν•˜μ—¬ μƒˆλ‘œμš΄ 객체λ₯Ό μƒμ„±ν•˜κ³ μž ν• λ•Œ μ‚¬μš©ν•©λ‹ˆλ‹€. μ†μ„±μ˜ 값은 true/false μž…λ‹ˆλ‹€.

     PostBuilder postBuilder = post1.toBuilder();
     Post update_title = postBuilder.title("update title")
       .build();
    

    κΈ°μ‘΄ post1μ—μ„œ title을 μ—…λ°μ΄νŠΈ ν•˜κ³ μž ν•  λ•Œ, toBuilder()둜 Builderλ₯Ό μƒμ„±ν•˜κ³  title을 μƒˆλ‘œ μ„ΈνŒ…ν•©λ‹ˆλ‹€.

    β“κ·ΈλŸ°λ°, μ΄λŸ°μ‹μœΌλ‘œ 객체의 μƒνƒœλ₯Ό μ—…λ°μ΄νŠΈ 할꺼라면 Setterλ₯Ό μ“°λŠ”κ²Œ 낫지 μ•Šμ„κΉŒ μ‹Άλ„€μš”. (ν˜Ήμ€ 객체의 μƒνƒœλ₯Ό λ³€κ²½ν•˜λŠ” λ©”μ†Œλ“œλ₯Ό λ”°λ‘œ λ§Œλ“€κ±°λ‚˜) Builderλ₯Ό μƒμ„±ν•˜κΈ° μœ„ν•΄ λΆˆν•„μš”ν•œ λΉ„μš©κ³Ό μ½”λ“œλΌμΈμ΄ μΆ”κ°€λ˜κΈ° λ•Œλ¬Έμ— λΆˆν•„μš”ν•˜λ‹€ μƒκ°ν•˜κ³  λ³„λ‘œ μ‚¬μš©λ  일이 μ—†μ–΄λ³΄μž…λ‹ˆλ‹€.

  4. accessλŠ” Builder 클래슀의 μ ‘κ·Όμ œν•œμžλ₯Ό μ„€μ •ν•œλ‹€. AccessLevel의 enum을 μ‚¬μš©ν•©λ‹ˆλ‹€.

πŸ’‘builderμ—μ„œ required와 optional을 μ„€μ •ν•˜κΈ° μœ„ν•œ μ½”λ“œ μž‘μ„± 방법을 λ‹€μŒκ³Ό 같이 μ‚¬μš©ν•˜λ©΄ λ©λ‹ˆλ‹€.

@Builder(builderMethodName = "allBuilder")
public class Post {

    public static PostBuilder builder(String title, String content) {
        return allBuilder()
            .title(title)
            .content(content);
    }
}

마무리


이번 ν¬μŠ€νŠΈμ—μ„  Builder νŒ¨ν„΄μ— λŒ€ν•΄ μ•Œμ•„λ΄€μŠ΅λ‹ˆλ‹€. Builder νŒ¨ν„΄μ€ λ¬΄λΆ„λ³„ν•œ Setterλ₯Ό μ§€μ–‘ν•˜κ³ , λΆˆλ³€ 객체λ₯Ό μœ μ§€ν•˜κΈ° μœ„ν•΄ 주둜 μ‚¬μš©ν•©λ‹ˆλ‹€.

ν•˜μ§€λ§Œ, Builder νŒ¨ν„΄μ„ μ μš©ν•  μƒμ„±μžμ˜ μΈμžκ°’μ΄ 1개, ν˜Ήμ€ 2개 λ“± κ°―μˆ˜κ°€ 적닀면 였히렀 μ•ˆμ’‹μ€ 상황이 λ‚˜μ˜¬μˆ˜ μžˆμŠ΅λ‹ˆλ‹€.

λ”°λΌμ„œ Builder νŒ¨ν„΄μ€ μΈμžκ°€ 4개, 적어도 3개 μ΄μƒλ˜μ—ˆμ„λ•Œ μ‚¬μš©ν•˜λŠ” 것을 ꢌμž₯ν•©λ‹ˆλ‹€.

2022λ…„ μƒˆν•΄κ°€ λ°μ•˜μŠ΅λ‹ˆλ‹€~ μƒˆν•΄μ—” λ‹€λ“€ μ›ν•˜λŠ” 것을 μ΄λ£¨μ‹œκ³ , 무엇보닀 κ±΄κ°•ν•˜μ‹œκΈΈ λ°”λžλ‹ˆλ‹€.

μ΄λ²ˆμ—λ„ 제 포슀트λ₯Ό 읽어주신 λΆ„λ“€ κ°μ‚¬ν•©λ‹ˆλ‹€. πŸ˜€

λŒ“κΈ€λ‚¨κΈ°κΈ°