PostCover

디자인 패턴을 쉽게 풀어보자 - 빌터 패턴편

GoF의 23가지 디자인 패턴 중 하나인 '빌더 패턴(Builder Pattern)'을 쉽게 풀어보기

빌더(Builder) 패턴

빌더 패턴은 복잡한 객체 생성을 단계별로 처리하며, 객체 생성 과정과 객체의 표현 방법을 분리하는 패턴입니다. 이 패턴은 다양한 속성을 가진 객체를 생성할 때, 매개변수의 수가 많아 코드가 복잡해지거나 가독성이 떨어지는 문제를 해결합니다.

문제점: 복잡한 객체 생성과 가독성 저하

아래 예제는 생성자(Constructor)를 통해 객체를 생성하는 방식입니다. 만약 객체가 많은 속성을 가진다면, 생성자에 매개변수가 너무 많아져 코드의 가독성이 떨어지고 유지보수가 어려워집니다.

class User {
    private String name;
    private String email;
    private String address;
    private String phoneNumber;
    private String jobTitle;

    public User(String name, String email, String address, String phoneNumber, String jobTitle) {
        this.name = name;
        this.email = email;
        this.address = address;
        this.phoneNumber = phoneNumber;
        this.jobTitle = jobTitle;
    }

    @Override
    public String toString() {
        return "User{name='" + name + "', email='" + email + "', address='" + address + "', phoneNumber='" + phoneNumber + "', jobTitle='" + jobTitle + "'}";
    }
}

생성자에 너무 많은 매개변수를 전달해야 하며, 매개변수 순서를 잘못 전달할 가능성이 높고, 선택적인 매개변수에 대해 모든 조합을 지원하려면 생성자 오버로딩이 필요합니다.

해결 방법: 빌더 패턴 적용

Builder 패턴은 객체 생성 로직을 분리하여 단계별로 명확하게 속성을 설정하도록 합니다. 이 패턴을 사용하면 매개변수 순서에 상관없이 객체를 생성할 수 있고, 필수/선택 매개변수를 유연하게 설정할 수 있습니다.

class User {
    private String name;
    private String email;
    private String address;
    private String phoneNumber;
    private String jobTitle;

    // private 생성자
    private User(UserBuilder builder) {
        this.name = builder.name;
        this.email = builder.email;
        this.address = builder.address;
        this.phoneNumber = builder.phoneNumber;
        this.jobTitle = builder.jobTitle;
    }

    @Override
    public String toString() {
        return "User{name='" + name + "', email='" + email + "', address='" + address + "', phoneNumber='" + phoneNumber + "', jobTitle='" + jobTitle + "'}";
    }

    // Builder 클래스 (내부 정적 클래스)
    public static class UserBuilder {
        private String name;
        private String email;
        private String address;
        private String phoneNumber;
        private String jobTitle;

        // 필수 매개변수 설정
        public UserBuilder(String name, String email) {
            this.name = name;
            this.email = email;
        }

        // 선택 매개변수 설정 메서드
        public UserBuilder setAddress(String address) {
            this.address = address;
            return this;
        }

        public UserBuilder setPhoneNumber(String phoneNumber) {
            this.phoneNumber = phoneNumber;
            return this;
        }

        public UserBuilder setJobTitle(String jobTitle) {
            this.jobTitle = jobTitle;
            return this;
        }

        // User 객체 생성 메서드
        public User build() {
            return new User(this);
        }
    }
}

이때의 클라이언트 코드는 다음과 같습니다.

public class Main {
    public static void main(String[] args) {
        User user = new User.UserBuilder("으악이", "euak@example.com")
                .setAddress("seoul")
                .setPhoneNumber("010-1234-5678")
                .setJobTitle("백수")
                .build();

        System.out.println(user);
        // 출력: User{name='으악이', email='euak@example.com', address='seoul', phoneNumber='010-1234-5678', jobTitle='백수'}
    }
}

빌더 패턴의 장단점

빌더 패턴은 메서드 체이닝 방식으로 객체의 속성을 설정하므로 코드가 명확하고 직관적입니다. 그리고 선택적인 매개변수에 대해 생성자 오버로딩 없이 필요한 속성만 설정할 수 있습니다. 또한, 객체가 생성된 후에는 수정되지 않도록 설계할 수 있고, 선택적인 매개변수를 자유롭게 설정할 수 있으며, 필요한 매개변수만 설정하도록 유도합니다.

그러나 객체를 생성하기 위해 빌더 클래스가 필요하므로 코드가 길어지고 클래스가 늘어날 수 있습니다. 또한, 간단한 객체 생성에는 오히려 불필요한 복잡성을 초래할 수 있습니다.

결론

빌더 패턴은 매개변수가 많거나 복잡한 객체를 유연하고 직관적으로 생성할 수 있게 도와주는 강력한 패턴입니다. 객체 생성 과정을 명확하게 표현하며, 불변 객체를 쉽게 만들 수 있는 장점이 있습니다. 하지만, 작은 객체나 간단한 객체 생성에는 오히려 복잡성이 증가할 수 있으므로 상황에 따라 적절히 사용하는 것이 중요합니다.