티스토리 뷰

이전에 살펴본 정적 팩토리 메서드 패턴은 분명 장점이 많습니다.
자바의 JDBC에서 사용하고 있는 패턴이기도 하고, OCP 원칙을 지키며 확장에도 열려있는 코드를 만들어줄 수 있습니다. 

하지만, 정적 팩토리 메서드는 생성자를 사용할 경우 발생하는 제약 사항이 그대로 존재하는데요, 선택적 매개변수가 많을 때 적절히 대응하기 어렵다는 문제입니다. 예를들어 아래와 같은 회원 클래스가 있다고 가정해보겠습니다.

public static class Users {
    private int age;                 // 필수
    private String name;             // 필수
    private String nickname;         // 선택
    private String phone_number;     // 선택
    private String address;          // 선택
}

회원 클래스의 age, name은 필수 필드이고 nickname, phone_number, address는 선택 필드라고 가정합니다. 이제 인스턴스를 생성하기 위해서 생성자나 정적 팩토리 메서드 패턴을 선택하여 적용할 수 있습니다.
이때 가능한 모든 경우의 수는 8개이므로 8개의 생성자 or 8개의 메서드를 만들어야 하는 문제가 생깁니다.

이 문제를 해결하기 위해 '점층적 생성자 패턴(Telescoping Constructor Pattern)'이 등장했습니다.
점층적 생성자 패턴은 필수 매개변수만 받는 생성자부터 시작하여 선택 매개변수 1개를 받는 생성자, 선택 매개변수를 2개까지 받는 생성자 ... 형태로 선택 매개변수를 전부 다 받는 생성자까지 늘려가는 방식입니다. 코드로 나타내면 아래와 같습니다.

public static class Users {
    private int age;                 // 필수
    private String name;             // 필수
    private String nickname;         // 선택
    private String phone_number;     // 선택
    private String address;          // 선택


    public Users(int age, String name) {
        this(age, name, "Null");
    }

    public Users(int age, String name, String nickname) {
        this(age, name, nickname, "Null");
    }

    public Users(int age, String name, String nickname, String phone_number) {
        this(age, name, nickname, phone_number, "Null");
    }

    public Users(int age, String name, String nickname, String phone_number, String address) {
        this.age = age;
        this.name = name;
        this.nickname = nickname;
        this.phone_number = phone_number;
        this.address = address;
    }
}

이러한 점층적 생성자 패턴에서 클래스의 인스턴스를 만들려면 원하는 매개변수를 모두 포함한 생성자 중 가장 짧은 것을 골라 호출하면 됩니다.

이 예시에서는 매개변수가 최대 5개이므로, 그리 나빠 보이지는 않을 수 있습니다. 하지만 이 숫자가 늘어나면 걷잡을 수 없을 만큼 커지게 됩니다. 또한 필드 하나를 지워야 한다는 요구사항이 들어온다면? 엄청난 수정이 필요할 것입니다.

결국 점층적 생성자 패턴도 매개변수 개수가 많아지면, 이에 비례하여 생성자 코드를 만들어야 하는 문제가 존재합니다. 또한 코드를 읽을 때 각 값의 의미가 무엇인지 헷갈릴 것이고, 매개변수가 몇 개인지도 주의해서 세어 보아야 하기에 클라이언트 코드를 작성하거나 읽기 어렵습니다.

이번에는 선택 매개변수가 많을 때 활용할 수 있는 또 다른 방법인 '자바빈즈 패턴(JavaBeans Pattern)'을 살펴보겠습니다.
자바빈즈 패턴은 매개변수가 없는 생성자로 객체를 만든 후 setter 메서드들을 호출해 원하는 매개변수의 값을 설정하는 방식입니다.

public static class Users {
    private int age;                          // 필수 기본값 없음
    private String name;                      // 필수 기본값 없음
    private String nickname = "Null";         // 선택
    private String phone_number = "Null";     // 선택
    private String address = "Null";          // 선택

    public Users() {
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public void setPhone_number(String phone_number) {
        this.phone_number = phone_number;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

이제 더이상 점층적 생성자 패턴의 단점들이 자바빈즈 패턴에서는 보이지 않습니다.
코드가 조금 더 길어지긴 했지만, 인스턴스를 만들기 쉬워졌고 그 결과 더 읽기 쉬운 코드가 되었습니다.

하지만, 자바빈즈 패턴은 불행하게도 심각한 단점을 가지고 있습니다.
자바빈즈 패턴에서는 객체 하나를 만들려면 메서드를 여러개 호출해야 하고, 객체가 완전히 생성되기 전까지는 '일관성(Consistency)'이 무너진 상태에 놓이게 됩니다.

쉽게 말해 멀티 스레드 환경에서 객체를 생성할 때 문제가 생길 수 있는 것입니다. 이는 아주 심각한 버그로 이어질 수 있고, 문제의 원인을 찾기도 쉽지가 않습니다.

'빌더 패턴(Builder Pattern)'은 위에서 살펴본 모든 패턴의 문제점들을 개선할 수 있습니다. 
빌더 패턴을 사용하면, 클라이언트는 필요한 객체를 직접 만드는 대신, 필수 매개변수만으로 생성자를 호출해 빌더 객체를 얻습니다. 그런 다음 빌더 객체가 제공하는 일종의 setter 메서드로 원하는 선택 매개변수들을 설정합니다. 빌더 패턴을 한번 사용해 봅시다.

public static class Users {
    private final int age;                 // 필수 기본값 없음
    private final String name;             // 필수 기본값 없음
    private final String nickname;         // 선택
    private final String phone_number;     // 선택
    private final String address;          // 선택



    private Users(Builder builder) {
        this.age = builder.age;
        this.name = builder.name;
        this.nickname = builder.nickname;
        this.phone_number = builder.phone_number;
        this.address = builder.address;
    }

    public static class Builder {
        // 필수 매개변수
        private final int age;
        private final String name;

        // 선택 매개변수 - 기본으로 초기화
        private String nickname = "Null";
        private String phone_number = "Null";
        private String address = "Null";

        public Builder(int age, String name) {
            this.age = age;
            this.name = name;
        }

        public Builder nickname(String nickname) {
            this.nickname = nickname;
            return this;
        }

        public Builder phone_number(String phone_number) {
            this.phone_number = phone_number;
            return this;
        }

        public Builder address(String address) {
            this.address = address;
            return this;
        }
        
        public Users build() {
            return new Users(this);
        }
    }
}

Builder의 setter 메서드들은 Builder 자신을 return 하기 때문에 연쇄적으로 호출할 수 있습니다. 이런 방식을 메서드 호출이 흐르듯 연결된다는 뜻으로 Method Chaining라고 부릅니다. 
아래의 코드는 이렇게 생성한 Builder 패턴을 사용하는 Client 코드입니다.

Users user = new Users.Builder(26, "Jo")
        .nickname("jaeng2")
        .phone_number("010-4902")
        .address("Busan")
        .build();

빌더 패턴은 쓰기 쉽고, 무엇보다도 매우 직관적입니다. 

하지만, 빌더 패턴에도 단점이 존재하는데, 객체를 만드려면 빌더부터 만들어야 한다는 문제입니다. 빌더 생성의 비용은 그리 크지 않지만, 성능에 민감한 상황에서는 문제가 될 수도 있습니다. 
또한 점층적 생성자 패턴보다는 코드가 장황해서 매개변수가 4개 이상은 되어야 빌더 패턴을 사용한 의미가 좀더 부각됩니다. 

 

Ref : 이펙티브 자바 Effective Java 3/E

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함