Joshua Bloch's Builder Pattern - kuyeol/Document GitHub Wiki

효과적인 Java 객체 생성을 μœ„ν•œ Builder νŒ¨ν„΄ κ°€μ΄λ“œ

Builder νŒ¨ν„΄μ˜ κ°œλ…, μž₯점, μ‹€μ œ μ½”λ“œ 예제

[!Tip] λ§Žμ€ λ§€κ°œλ³€μˆ˜λ₯Ό κ°€μ§„ 객체λ₯Ό μ–΄λ–»κ²Œ μ•ˆμ „ν•˜κ³  가독성 λ†’κ²Œ 생성할 수 μžˆμ„κΉŒ?
Joshua Blochκ°€ γ€ŽEffective Javaγ€μ—μ„œ μ œμ•ˆν•œ Builder νŒ¨ν„΄μ€
"ν…”λ ˆμŠ€μ½”ν•‘ μƒμ„±μž"의 단점을 κ·Ήλ³΅ν•˜κ³ ,
λ³΅μž‘ν•œ 객체 생성을 더 읽기 쉽고 μœ μ—°ν•˜λ©° μ•ˆμ „ν•˜κ²Œ λ§Œλ“€μ–΄μ€λ‹ˆλ‹€.

1. 문제점: ν…”λ ˆμŠ€μ½”ν•‘ μƒμ„±μžμ™€ κ·Έ ν•œκ³„

μžλ°”μ—μ„œ μ—¬λŸ¬ λ§€κ°œλ³€μˆ˜κ°€ ν•„μš”ν•œ 객체λ₯Ό 생성할 λ•Œ,
"ν…”λ ˆμŠ€μ½”ν•‘ μƒμ„±μž(Telescoping Constructor) νŒ¨ν„΄" 을 ν”νžˆ μ‚¬μš©ν•©λ‹ˆλ‹€.
ν•˜μ§€λ§Œ, 이 방식은 λ‹€μŒκ³Ό 같은 문제λ₯Ό μ•ˆκ³  μžˆμŠ΅λ‹ˆλ‹€.

[!caution] ν…”λ ˆμŠ€μ½”ν•‘ μƒμ„±μž νŒ¨ν„΄
선택적 λ§€κ°œλ³€μˆ˜κ°€ λŠ˜μ–΄λ‚ μˆ˜λ‘ μƒμ„±μž μ˜€λ²„λ‘œλ”©μ΄ ν­μ¦ν•˜κ³ ,
μ½”λ“œκ°€ λΆˆν•„μš”ν•˜κ²Œ λ³΅μž‘ν•΄μ§‘λ‹ˆλ‹€.
특히, λ§€κ°œλ³€μˆ˜κ°€ 많고 νƒ€μž…μ΄ κ°™μœΌλ©΄ μ‹€μˆ˜ν•  μœ„ν—˜μ΄ λ†’μ•„μ§‘λ‹ˆλ‹€.

μ˜ˆμ‹œ: Book 클래슀의 ν…”λ ˆμŠ€μ½”ν•‘ μƒμ„±μž

// μ•ˆν‹° νŒ¨ν„΄: ν…”λ ˆμŠ€μ½”ν•‘ μƒμ„±μž
public class Book {
    private final String isbn;      // ν•„μˆ˜
    private final String title;     // ν•„μˆ˜
    private final String author;    // 선택
    private final int publicationYear; // 선택
    private final String description; // 선택

    public Book(String isbn, String title) {
        this(isbn, title, null);
    }
    public Book(String isbn, String title, String author) {
        this(isbn, title, author, 0);
    }
    public Book(String isbn, String title, String author, int publicationYear) {
        this(isbn, title, author, publicationYear, "N/A");
    }
    public Book(String isbn, String title, String author, int publicationYear, String description) {
        this.isbn = isbn;
        this.title = title;
        this.author = author;
        this.publicationYear = publicationYear;
        this.description = description;
    }
    @Override
    public String toString() {
        return "Book{" +
               "isbn='" + isbn + '\'' +
               ", title='" + title + '\'' +
               ", author='" + author + '\'' +
               ", publicationYear=" + publicationYear +
               ", description='" + description + '\'' +
               '}';
    }
}
public class Main {
    public static void main(String[] args) {
        // ν•„μˆ˜ λ§€κ°œλ³€μˆ˜λ§Œ
        Book book1 = new Book("978-0321765723", "Effective Java");
        // λͺ¨λ“  λ§€κ°œλ³€μˆ˜
        Book book2 = new Book("978-1617294945", "Spring in Action", "Craig Walls", 2018, "A comprehensive guide to Spring Framework.");
        // 일뢀 선택 λ§€κ°œλ³€μˆ˜
        Book book3 = new Book("978-0134685991", "Clean Code", "Robert C. Martin", 2008);

        System.out.println(book1);
        System.out.println(book2);
        System.out.println(book3);
    }
}

좜λ ₯:

Book{isbn='978-0321765723', title='Effective Java', author='null', publicationYear=0, description='N/A'}
Book{isbn='978-1617294945', title='Spring in Action', author='Craig Walls', publicationYear=2018, description='A comprehensive guide to Spring Framework.'}
Book{isbn='978-0134685991', title='Clean Code', author='Robert C. Martin', publicationYear=2008, description='N/A'}

단점 μš”μ•½

[!warning]

  • 가독성 μ €ν•˜: λ§€κ°œλ³€μˆ˜ μˆœμ„œμ™€ 의미λ₯Ό νŒŒμ•…ν•˜κΈ° μ–΄λ ΅κ³ , μ‹€μˆ˜ν•˜κΈ° 쉽닀.
  • μœ μ§€λ³΄μˆ˜ 어렀움: μƒˆλ‘œμš΄ 선택 λ§€κ°œλ³€μˆ˜ μΆ”κ°€ μ‹œ μƒμ„±μž μΆ”κ°€Β·μˆ˜μ • ν•„μš”.
  • null의 λͺ¨ν˜Έν•¨: 선택 ν•„λ“œμ— null 전달 μ‹œ μ½”λ“œμ˜ μ˜λ„κ°€ λΆˆλΆ„λͺ….
  • μƒμ„±μž 폭증: μ‘°ν•©λ§ŒνΌ μƒμ„±μžκ°€ λŠ˜μ–΄λ‚˜ ν΄λž˜μŠ€κ°€ λΉ„λŒ€ν•΄μ§.

2. ν•΄κ²°μ±…: Joshua Bloch의 Builder νŒ¨ν„΄

[!note] Builder νŒ¨ν„΄μ€
객체 생성 과정을 μ—¬λŸ¬ λ‹¨κ³„λ‘œ λͺ…ν™•νžˆ λ‚˜λˆ„μ–΄
가독성과 μ•ˆμ „μ„±μ„ λͺ¨λ‘ μž‘μ„ 수 있게 ν•΄μ€λ‹ˆλ‹€.

핡심 아이디어

[!tip]

  • 객체 내뢀에 static nested class둜 Builder μ •μ˜
  • μ›λž˜ 객체의 μƒμ„±μžλŠ” private
  • ν•„μˆ˜ ν•„λ“œλŠ” Builder μƒμ„±μžμ—μ„œ, 선택 ν•„λ“œλŠ” λ©”μ†Œλ“œ μ²΄μ΄λ‹μœΌλ‘œ
  • λ§ˆμ§€λ§‰μ— build()둜 객체 생성

예제: Book ν΄λž˜μŠ€μ— Builder νŒ¨ν„΄ 적용

import java.time.Year;

public class Book {
    private final String isbn;          // ν•„μˆ˜
    private final String title;         // ν•„μˆ˜
    private final String author;        // 선택
    private final Year publicationYear; // 선택
    private final String description;   // 선택

    // 1. private μƒμ„±μž (Builder둜만 생성)
    private Book(Builder builder) {
        this.isbn = builder.isbn;
        this.title = builder.title;
        this.author = builder.author;
        this.publicationYear = builder.publicationYear;
        this.description = builder.description;
    }

    @Override
    public String toString() {
        return "Book{" +
               "isbn='" + isbn + '\'' +
               ", title='" + title + '\'' +
               ", author='" + author + '\'' +
               ", publicationYear=" + publicationYear +
               ", description='" + description + '\'' +
               '}';
    }

    public static class Builder {
        private final String isbn;
        private final String title;
        private String author = null;
        private Year publicationYear = null;
        private String description = "No description available.";

        public Builder(String isbn, String title) {
            if (isbn == null || title == null) {
                throw new IllegalArgumentException("ISBN and Title cannot be null.");
            }
            this.isbn = isbn;
            this.title = title;
        }

        public Builder author(String author) {
            this.author = author;
            return this;
        }
        public Builder publicationYear(Year publicationYear) {
            this.publicationYear = publicationYear;
            return this;
        }
        public Builder description(String description) {
            this.description = description;
            return this;
        }

        public Book build() {
            return new Book(this);
        }
    }
}
public class Main {
    public static void main(String[] args) {
        // ν•„μˆ˜ λ§€κ°œλ³€μˆ˜λ§Œ
        Book book1 = new Book.Builder("978-0321765723", "Effective Java").build();

        // λͺ¨λ“  λ§€κ°œλ³€μˆ˜
        Book book2 = new Book.Builder("978-1617294945", "Spring in Action")
                            .author("Craig Walls")
                            .publicationYear(Year.of(2018))
                            .description("A comprehensive guide to Spring Framework.")
                            .build();

        // 일뢀 선택 λ§€κ°œλ³€μˆ˜ (μˆœμ„œ 무관)
        Book book3 = new Book.Builder("978-0134685991", "Clean Code")
                            .publicationYear(Year.of(2008))
                            .author("Robert C. Martin")
                            .build();

        System.out.println(book1);
        System.out.println(book2);
        System.out.println(book3);
    }
}

좜λ ₯:

Book{isbn='978-0321765723', title='Effective Java', author='null', publicationYear=null, description='No description available.'}
Book{isbn='978-1617294945', title='Spring in Action', author='Craig Walls', publicationYear=2018, description='A comprehensive guide to Spring Framework.'}
Book{isbn='978-0134685991', title='Clean Code', author='Robert C. Martin', publicationYear=2008, description='No description available.'}

3. Builder νŒ¨ν„΄ μ‚¬μš© 방법

μœ„ μ½”λ“œμ™€ 같이 Builder νŒ¨ν„΄μ„ μ‚¬μš©ν•˜λ©΄,
ν•„μˆ˜ λ§€κ°œλ³€μˆ˜λ§Œ μ‚¬μš©ν•  μˆ˜λ„ 있고,
ν•„μš”μ— 따라 일뢀 λ˜λŠ” λͺ¨λ“  선택 λ§€κ°œλ³€μˆ˜λ₯Ό 자유둭게 μΆ”κ°€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

[!note] μ‹€ν–‰ κ²°κ³Ό

  • ν•„μˆ˜ κ°’λ§Œ μ§€μ • μ‹œ: author, publicationYear, description은 κΈ°λ³Έκ°’
  • 일뢀 κ°’λ§Œ μ§€μ • μ‹œ: μ›ν•˜λŠ” κ°’λ§Œ 체이닝
  • λͺ¨λ“  κ°’ μ§€μ • μ‹œ: λͺ¨λ“  ν•„λ“œ κ°’ μž…λ ₯ κ°€λŠ₯

4. Builder νŒ¨ν„΄μ˜ μž₯점 μš”μ•½

[!important] μž₯점 정리

  • 가독성: μ–΄λ–€ ν•„λ“œμ— μ–΄λ–€ 값이 λ“€μ–΄κ°€λŠ”μ§€ λͺ…ν™•
  • μœ μ—°μ„±: ν•„μš”ν•œ ν•„λ“œλ§Œ μ„ νƒμ μœΌλ‘œ, μˆœμ„œμ— 상관없이 μ§€μ • κ°€λŠ₯
  • λΆˆλ³€ 객체 생성: build() μ „κΉŒμ§€λŠ” λ―Έμ™„μ„±, 생성 ν›„μ—” λΆˆλ³€(final)
  • μœ νš¨μ„± 검사: build()μ—μ„œ λͺ¨λ“  ν•„λ“œμ˜ μœ νš¨μ„± 일괄 검사 κ°€λŠ₯
  • μƒμ„±μž λ‚œλ¦½ λ°©μ§€: μƒμ„±μž 폭증 μ—†μŒ

5. μ–Έμ œ Builder νŒ¨ν„΄μ„ μ„ νƒν• κΉŒ?

[!important] Builder νŒ¨ν„΄ 적용 μΆ”μ²œ 상황

  • μƒμ„±μž/정적 νŒ©ν† λ¦¬ λ©”μ†Œλ“œ νŒŒλΌλ―Έν„°κ°€ 4개 이상일 λ•Œ
  • 선택적 λ§€κ°œλ³€μˆ˜κ°€ λ§Žμ„ λ•Œ
  • 객체가 **λΆˆλ³€(immutable)**이어야 ν•  λ•Œ
  • 객체 생성 과정이 λ³΅μž‘ν•˜κ±°λ‚˜ 단계적일 λ•Œ

6. Record && Builder νŒ¨ν„΄ μ‘°ν•©

Java의 λ ˆμ½”λ“œλŠ” λΆˆλ³€ 데이터 객체 생성을 κ°„λ‹¨ν•˜κ²Œ ν•΄μ£Όμ§€λ§Œ,
선택적 ν•„λ“œκ°€ 많고 μœ μ—°ν•œ 생성이 ν•„μš”ν•˜λ‹€λ©΄ μ—¬μ „νžˆ λΉŒλ” νŒ¨ν„΄μ΄ μœ μš©ν•©λ‹ˆλ‹€.

[!note] λ ˆμ½”λ“œμ™€ λΉŒλ” λ™μ‹œ μ‚¬μš©

  • λ ˆμ½”λ“œλŠ” λͺ¨λ“  ν•„λ“œλ₯Ό ν•œ λ²ˆμ— μ„ΈνŒ…, μƒμ„±μž/νŒ©ν† λ¦¬μ—μ„œ λΉŒλ” ν™œμš© κ°€λŠ₯
  • 선택적 ν•„λ“œκ°€ λ§Žκ±°λ‚˜, λΉŒλ” μŠ€νƒ€μΌ APIλ₯Ό 원할 λ•Œ 유용
import java.time.Year;

public record BookRecord(String isbn, String title, String author, Year publicationYear, String description) {

    public static class Builder {
        private final String isbn;
        private final String title;
        private String author;
        private Year publicationYear;
        private String description;

        public Builder(String isbn, String title) {
            this.isbn = isbn;
            this.title = title;
        }

        public Builder author(String author) { this.author = author; return this; }
        public Builder publicationYear(Year year) { this.publicationYear = year; return this; }
        public Builder description(String desc) { this.description = desc; return this; }

        public BookRecord build() {
            return new BookRecord(isbn, title, author, publicationYear, description);
        }
    }

    public static Builder builder(String isbn, String title) {
        return new Builder(isbn, title);
    }
}
public class Main {
    public static void main(String[] args) {
        // ν•„μˆ˜ λ§€κ°œλ³€μˆ˜λ§Œ
        BookRecord record1 = BookRecord.builder("978-0321765723", "Effective Java").build();

        // λͺ¨λ“  λ§€κ°œλ³€μˆ˜
        BookRecord record2 = BookRecord.builder("978-1617294945", "Spring in Action")
                            .author("Craig Walls")
                            .publicationYear(Year.of(2018))
                            .description("A comprehensive guide to Spring Framework.")
                            .build();

        // 일뢀 선택 λ§€κ°œλ³€μˆ˜
        BookRecord record3 = BookRecord.builder("978-0134685991", "Clean Code")
                            .author("Robert C. Martin")
                            .publicationYear(Year.of(2008))
                            .build();

        System.out.println(record1);
        System.out.println(record2);
        System.out.println(record3);
    }
}

좜λ ₯:

BookRecord[isbn=978-0321765723, title=Effective Java, author=null, publicationYear=null, description=null]
BookRecord[isbn=978-1617294945, title=Spring in Action, author=Craig Walls, publicationYear=2018, description=A comprehensive guide to Spring Framework.]
BookRecord[isbn=978-0134685991, title=Clean Code, author=Robert C. Martin, publicationYear=2008, description=null]

κ²°λ‘ 

[!important]

  • λ³΅μž‘ν•œ 객체λ₯Ό μš°μ•„ν•˜κ²Œ μƒμ„±ν•˜κ³ 
  • μ½”λ“œμ˜ 가독성을 높이며
  • λΆˆλ³€ 객체λ₯Ό μ‰½κ²Œ λ§Œλ“€ 수 있음

λ§€κ°œλ³€μˆ˜κ°€ 많고, 일뢀가 μ˜΅μ…˜μ΄λΌλ©΄ λΉŒλ” νŒ¨ν„΄μ„ 적극적으둜 적용!


μ°Έκ³ :