Builder Pattern

λ³΅μž‘ν•œ 객체에 λŒ€ν•΄ 생성(contruction)κ³Ό ν‘œν˜„(representation)을 λΆ„λ¦¬ν•¨μœΌλ‘œμ¨ λ˜‘κ°™μ€ 생성 κ³Όμ •μœΌλ‘œ μ„œλ‘œ λ‹€λ₯Έ 객체 ν‘œν˜„μ„ κ°€λŠ₯ν•˜κ²Œ ν•˜λŠ” 생성 λ””μžμΈ νŒ¨ν„΄


Builder νŒ¨ν„΄μ„ μ‚¬μš©ν•΄μ•Όν•˜λŠ” 이유

  1. Immutability - 객체의 λΆˆλ³€μ„±μ„ μœ μ§€ν•  수 있음
  2. Named Parameter with Chaining - 체이닝을 ν†΅ν•œ λͺ…λͺ…λœ λ§€κ°œλ³€μˆ˜ μ‚¬μš©μœΌλ‘œ 가독성 증진
  3. Design Flexibility - ν•„μˆ˜μ μΈ λ³€μˆ˜μ™€ 선택적인 λ³€μˆ˜λ₯Ό 각각 생성 κ°€λŠ₯
  4. Easy Maintenance - μƒˆλ‘œμš΄ 멀버가 μΆ”κ°€λ˜λ”λΌλ„ 기쑴의 객체 생성 μ½”λ“œλ₯Ό μˆ˜μ •ν•  ν•„μš” μ—†μŒ
  5. Avoid RuntimeException - 객체 생성 κ³Όμ •μ—μ„œ μœ νš¨μ„± 검사λ₯Ό 톡해 논리적인 μ—λŸ¬λ₯Ό 막을 수 있음

πŸ’‘ λΆˆλ³€μ μΈ 객체둜 κ΅¬ν˜„ν•΄μ•Όν•˜λŠ” 이유

  • λΆˆλ³€μ„±(Immutability)μ΄λž€? 객체가 μ΄ˆκΈ°μ— ν•œλ²ˆ μƒμ„±λœ μ΄ν›„μ—λŠ” μ ˆλŒ€ μƒνƒœλ₯Ό 바꾸지 μ•ŠλŠ” 것을 λ§ν•œλ‹€. 객체 μƒμ„±μ‹œμ— λͺ¨λ“  정보가 주어지고 객체의 생애 μ£ΌκΈ° λ™μ•ˆμ—λŠ” μƒνƒœκ°€ λ°”λ€Œμ§€ μ•ŠλŠ” 것이 νŠΉμ§•μ΄λ‹€.
  • μ‚¬μš©μ΄ 쉽닀
  • Thread Safe ν•˜λ‹€. 동기화할 ν•„μš”κ°€ μ—†λ‹€.
  • 자유둭게 κ³΅μœ ν•  수 μžˆλ‹€.

Builder νŒ¨ν„΄μ˜ ν•œκ³„

μ½”λ“œλ₯Ό 2배정도 많이 μ‚¬μš©ν•˜κ²Œ λœλ‹€. λ”°λΌμ„œ μ„€μ •ν•΄μ•Ό ν•  λ§€κ°œλ³€μˆ˜κ°€ 적을 κ²½μš°μ—λŠ” 일반 μƒμ„±μžλ₯Ό ν†΅ν•œ 생성이 λ”μš± νŽΈν•  μˆ˜λ„ μžˆλ‹€.


κ΅¬ν˜„

Builder νŒ¨ν„΄ 적용 μ „

μΌλ°˜μ μœΌλ‘œλŠ” μƒμ„±μž(Constructor)λ₯Ό 톡해 객체λ₯Ό 생성할 것이닀. μƒμ„±μžλ₯Ό μ‚¬μš©ν•  경우 멀버λ₯Ό μ„ νƒμ μœΌλ‘œ μƒμ„±ν•˜κΈ° μ–΄λ ΅λ‹€.

1. μƒμ„±μžλ₯Ό μ‚¬μš©ν•œ 생성 - μžλ°”λΉˆμ¦ˆ νŒ¨ν„΄(JavaBeans Pattern)

λ§€κ°œλ³€μˆ˜κ°€ μ—†λŠ” κΈ°λ³Έ μƒμ„±μžλ₯Ό 톡해 객체λ₯Ό μƒμ„±ν•œ λ’€, Setter λ©”μ„œλ“œλ₯Ό 톡해 멀버λ₯Ό μ„€μ •ν•˜λŠ” 방식이닀.

[클래슀 μ •μ˜]

public User() {

}

public User(String firstName, String lastName, int age, String phone, String address) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
    this.phone = phone;
    this.address = address;
}

[객체 생성]

User user1 = new User("ssafy", "Kim", 6, "02-666-6666", "μ„œμšΈμ‹œ 강남ꡬ ν…Œν—€λž€λ‘œ 212 λ©€ν‹°μΊ νΌμŠ€");
User user2 = new User("ssafy", "Lee", 5, null, null);

User user3 = new User();
user3.setFirstName("ssafy");
user3.setLastName("Choi");

2. μƒμ„±μžλ₯Ό μ‚¬μš©ν•œ 생성 - 점측적 μƒμ„±μž νŒ¨ν„΄(Telescoping Constructor Pattern)

ν•„μˆ˜ λ§€κ°œλ³€μˆ˜λ§Œμ„ 가진 μƒμ„±μžλ₯Ό λ§Œλ“€κ³  선택 λ§€κ°œλ³€μˆ˜λ₯Ό ν•˜λ‚˜μ”© μΆ”κ°€ν•œ μƒμ„±μžλ₯Ό λ§Œλ“ λ‹€. μˆ˜λ§Žμ€ μƒμ„±μž μ˜€λ²„λ‘œλ”©μ„ 톡해 μ›ν•˜λŠ” ν˜•νƒœμ˜ 객체λ₯Ό μƒμ„±ν•˜λ„λ‘ ν•˜λŠ” 방식이닀.

[객체 생성]

public User (String firstName, String lastName, int age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
    this.phone = null;
    this.address = null;
}

public User (String firstName, String lastName, int phone) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = null;
    this.phone = phone;
    this.address = null;
}

μœ„ μ˜ˆμ œμ™€ 같이 μƒμ„±μžλ₯Ό ν†΅ν•œ 객체 생성 μ‹œ ν•„μˆ˜ λ§€κ°œλ³€μˆ˜μ™€ 선택 λ§€κ°œλ³€μˆ˜λ₯Ό κ΅¬λΆ„ν•˜μ—¬ κ΅¬ν˜„ν•˜κΈ°κ°€ μ–΄λ ΅λ‹€. 특히 λ§€κ°œλ³€μˆ˜κ°€ λ§Žμ•„μ§„λ‹€λ©΄ 일일히 setterλ₯Ό λΆ€λ₯΄λŠ” 일도, λ§€κ°œλ³€μˆ˜ 자리λ₯Ό μ„Έμ£ΌλŠ” 것도 일이닀. μƒμ„±μžλ₯Ό 경우의 수 λ³„λ‘œ κ΅¬ν˜„ν•˜λŠ” 것은 λ”μš± 끔찍할 것이닀.

Builder νŒ¨ν„΄ 적용 ν›„

[클래슀 μ •μ˜]

// 클래슀λ₯Ό Final둜 μ„€μ •ν•˜μ—¬ ν™•μž₯이 λΆˆκ°€λŠ₯ν•˜λ©° λΆˆλ³€μ„±μ΄ μœ μ§€λ¨
public final class User 
{
    // λΆˆλ³€μ„±μ„ μœ μ§€ν•˜κΈ° μœ„ν•΄ private final둜 μ„€μ •
    private final String firstName;     // ν•„μˆ˜ λ³€μˆ˜
    private final String lastName;      // ν•„μˆ˜ λ³€μˆ˜
    private final int age;              // 선택 λ³€μˆ˜
    private final String phone;         // 선택 λ³€μˆ˜
    private final String address;       // 선택 λ³€μˆ˜

    private User(UserBuilder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.age = builder.age;
        this.phone = builder.phone;
        this.address = builder.address;
    }
 
    // Setterλ₯Ό κ΅¬ν˜„ν•˜μ§€ μ•ŠμŒμœΌλ‘œμ¨ λΆˆλ³€μ„± μœ μ§€
    public String getFirstName() {
        return firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public int getAge() {
        return age;
    }
    public String getPhone() {
        return phone;
    }
    public String getAddress() {
        return address;
    }
 
    @Override
    public String toString() {
        return "User: "+this.firstName+", "+this.lastName+", "+this.age+", "+this.phone+", "+this.address;
    }
 
    // 객체 내뢀에 Builder μ •μ˜(쀑첩 클래슀)
    public static class UserBuilder 
    {
        // ν•„μˆ˜μ μΈ λ³€μˆ˜λ§Œ final둜 μ„€μ •
        private final String firstName;     // ν•„μˆ˜ λ³€μˆ˜
        private final String lastName;      // ν•„μˆ˜ λ³€μˆ˜
        private int age;                    // 선택 λ³€μˆ˜
        private String phone;               // 선택 λ³€μˆ˜
        private String address;             // 선택 λ³€μˆ˜

        // Builder μƒμ„±μž λ§€κ°œλ³€μˆ˜λŠ” ν•„μˆ˜ λ³€μˆ˜λ§Œμ„ 포함
        public UserBuilder(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }
        // 선택적인 λ³€μˆ˜λŠ” 좔가적인 λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•˜μ—¬ 생성
        public UserBuilder age(int age) {
            this.age = age;
            return this;
        }
        public UserBuilder phone(String phone) {
            this.phone = phone;
            return this;
        }
        public UserBuilder address(String address) {
            this.address = address;
            return this;
        }
        // Builder둜 μƒμ„±λœ 객체 λ°˜ν™˜
        public User build() {
            User user =  new User(this);
            if (!validateUserName(user)) throw new NoNameException();
            if (!validateUserAge(user)) throw new InvalidAgeException();
            return user;
        }
        private boolean validateUserName(User user) {
            if (user.firstName==null || user.lastName==null) {
                if (user.age!=null || user.phone!=null || user.address!=null) return false;
            }
            return true;
        }
        private boolean validateUserAge(User user) {
            if (user.age<0) return false;
            return true;
        }
    }
}

[객체 생성]

User user1 = new User.UserBuilder("ssafy", "Kim")
                     .age(6)
                     .phone("02-666-6666")
                     .address("μ„œμšΈμ‹œ 강남ꡬ ν…Œν—€λž€λ‘œ 212 λ©€ν‹°μΊ νΌμŠ€")
                     .build();

User user2 = new User.UserBuilder("ssafy", "Lee")
                     .age(5)
                     // no phone
                     // no address
                     .build();

User user3 = new User.UserBuilder("ssafy", "Choi")
                     // no age
                     // no phone
                     // no address
                     .build();

λΉŒλ” νŒ¨ν„΄μ„ μ΄μš©ν•΄ μœ„μ™€ 같이 ν•˜λ‚˜μ˜ μƒμ„±μžλ§ŒμœΌλ‘œ μ—¬λŸ¬ μƒνƒœμ˜ 객체λ₯Ό 생성할 수 μžˆκ²Œλ˜μ—ˆλ‹€.

  • 멀버λ₯Ό final둜 μ„€μ •ν•˜μ—¬ λΆˆλ³€μ„±μ„ μœ μ§€ν•  수 μžˆλ‹€.
  • 선택적인 λ³€μˆ˜μ˜ 경우 null둜 μ„€μ •ν•  ν•„μš”κ°€ μ—†λ‹€. λ˜ν•œ μƒμ„±μž μ˜€λ²„λ‘œλ”©μ„ ν•˜μ§€ μ•Šμ•„ 선택적인 λ³€μˆ˜λ₯Ό 가진 객체도 λ™μΌν•œ λ°©λ²•μœΌλ‘œ 생성할 수 μžˆλ‹€.
  • 각 λ³€μˆ˜μ˜ 이름에 ν•΄λ‹Ήν•˜λŠ” λ©”μ„œλ“œλ₯Ό chanining λ°©μ‹μœΌλ‘œ μ ‘κ·Όν•˜μ—¬ μ΄ˆκΈ°ν™”ν•  수 μžˆλ‹€. λ”°λΌμ„œ μƒμ„±μžμ˜ λ§€κ°œλ³€μˆ˜ μˆœμ„œλ₯Ό κΈ°μ–΅ν•  ν•„μš”κ°€ μ—†κ³ , 생성 κ³Όμ •μ—μ„œμ˜ 가독성이 훨씬 쒋아진닀.
  • λ§Œμ•½ μƒˆλ‘œμš΄ 멀버 λ³€μˆ˜κ°€ μΆ”κ°€λ˜λ”λΌλ„ κΈ°μ‘΄ 객체 생성 μ½”λ“œλ₯Ό μˆ˜μ •ν•˜μ§€ μ•Šμ•„λ„ λœλ‹€. μƒˆλ‘­κ²Œ μΆ”κ°€λœ 멀버 λ³€μˆ˜λ„ 선택적인 λ§€κ°œλ³€μˆ˜μ™€ λ™μΌν•˜κ²Œ μ²˜λ¦¬ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.
  • μΆ”κ°€μ μœΌλ‘œ λΉŒλ” 클래슀 내뢀에 μœ νš¨μ„± 검사 λ©”μ„œλ“œλ₯Ό μΆ”κ°€ν•œλ‹€λ©΄ 멀버 생성 κ³Όμ •μ—μ„œμ˜ 논리적인 μ—λŸ¬λ₯Ό 사전에 차단할 수 μžˆλ‹€.

References

howtodoinjava Builder Pattern

dzone Immutability and Builder Pattern

StackExchange Why do we need a builder class