반응형

미처 발견하지 못한 부분들을 알아보고자 몇 가지 피드백을 받게 되었고,

이를 통해 구조를 개선하는 과정을 거쳐보도록 하겠습니다.

먼저 지금의 구조입니다.

빌더패턴 구조

지금은 Builder와 Step이 서로 의존하고 있는 관계입니다.

build()라는 과정도 독립적으로 구분하고자 Builder를 별도의 클래스로 작성했던 것인데,

다시 생각해보니 이 과정도 Step의 하나로 구분해줄 수 있었습니다.

이렇게 해서 다시 설계해본 구조는 아래와 같습니다.

빌더패턴 구조

각 필드 초기화 단계와 build 단계를 정의하는 4개의 인터페이스가 있고,

이를 구현하는 Builder 클래스가 자리잡고 있습니다.

Step과 Builder가 서로 상호의존하고 있던 이전의 방식에서 더욱 간결한 구조가 되었습니다.

각 단계마다 필드 초기화 또는 빌드를 수행하기때문에 Step이라는 이름이 더욱 어울리지않을까 싶었지만,

일반적인 관례와 본래의 목적에 따라서 이름을 Builder로 명명하였습니다.

(

정말 개인적인 생각이고 조심스러운 발언이지만, 하나의 인스턴스로 시작점에서 마지막까지 각 스텝을 강제적으로 밟아나가는 과정 또한 무언가 새로운 패턴을 발견한 것은 아닐까?! 싶습니다.

단일한 인스턴스로 클라이언트에게 각 절차를 강요하는게 어떤 실용적인 상황들이 있을지는 사실 잘 모르겠지만요. 다중 상속으로 서브타이핑을 지원하는 언어도 필요하겠고요. 또, 인터페이스를 추가할 때마다 구현 클래스나 클라이언트 코드를 수정해야해서 객체지향 원칙을 지킨다고 보기에도 어려울 것 같습니다.

수정을 줄이고 반환 값 활용과 같은 유연성을 위해, 클라이언트 입장에서는 단일 메소드만 호출하여 재귀적인 호출로 전체 스텝을 밟아나가는 방법도 있겠지만, 이럴 바에야 그냥 여러 책임들과 의존 관계를 가지는 메소드 하나를 호출하거나 데코레이터 방식(이제 보니 구조가 데코레이터를 뒤집은 것과 비슷하군요)이 낫겠네요.

이들과 비교했을 때의 장점은 역시 단일 인스턴스라는 것이고, 오히려 하나의 클래스가 너무 비대해질 수도 있겠습니다.

빌드하는 책임으로 모든 메소드들이 뭉쳐있는 지금의 상황처럼 각 단계들 간에 응집도가 높은 상황에서 절차를 강제할 때나 효용이 있을 것이라고 생각됩니다. 또는, 클라이언트 입장에서 수행해야하는 사전 또는 사후 조건을 명시적으로 강제해야하거나요.

물론, 모든 디자인패턴을 제대로 공부해본 것도 아니어서 이미 다른 해결책이 있을 수도 있고, 실용성에 대한 확신은 못하겠습니다. 무지에서 나온 발언 또는 고민의 흐름 정도로 여겨주시면 감사하겠습니다.

)

이렇게 해서 수정된 코드는 다음과 같습니다.

public class Person {
    private String firstName;
    private String lastName;
    private int age;
    private String phoneNumber;

    private Person(Builder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.age = builder.age;
        this.phoneNumber = builder.phoneNumber;
    }

    public interface FirstNameBuilder {
        LastNameBuilder firstName(String firstName);
    }

    public interface LastNameBuilder {
        AgeBuilder lastName(String lastName);
    }

    public interface AgeBuilder {
        PhoneNumberBuilder age(int age);
    }

    public interface PhoneNumberBuilder {
        BuildBuilder phoneNumber(String phoneNumber);
    }

    public interface BuildBuilder {
        Person build();
    }

    public static class Builder implements FirstNameBuilder, LastNameBuilder, AgeBuilder, PhoneNumberBuilder, BuildBuilder {
        private String firstName;
        private String lastName;
        private int age;
        private String phoneNumber;

        private Builder() {}

        public static FirstNameBuilder builder() {
            return new Builder();
        }

        @Override
        public LastNameBuilder firstName(String firstName) {
            this.firstName = firstName;
            return this;
        }

        @Override
        public AgeBuilder lastName(String lastName) {
            this.lastName = lastName;
            return this;
        }

        @Override
        public PhoneNumberBuilder age(int age) {
            this.age = age;
            return this;
        }

        @Override
        public BuildBuilder phoneNumber(String phoneNumber) {
            this.phoneNumber = phoneNumber;
            return this;
        }

        @Override
        public Person build() {
            return new Person(this);
        }
    }
}

클라이언트 입장에서의 코드 변경은 없고, 확실히 전체적인 구조가 간결해졌습니다.

이전 포스트에서 언급했던 2개의 인스턴스를 생성하는 문제는 완벽히 해결되었고,

기존 빌더 방식과 동일하게 하나의 인스턴스만 생성되는 상황입니다.

현재 이 문제는 해결되었지만, 다른 문제들(1편 참조)은 여전히 남아있습니다.

너무 많은 인터페이스, 인터페이스의 외부 노출, 선택적 필드 초기화 등 많은 문제점이 있습니다.

또, 중복 초기화 방지로 인해 메소드 호출을 제한하는 기능은 필요성 자체에 의문이 들기도 합니다.

이를 강제적으로 제한한다면, 중간에 필드가 추가되었을 경우 클라이언트 코드도 순서에 맞춰서 수정되어야하기 때문입니다. 정말 한치의 실수도 용납되지않는 엄격한 상황에서나 제한하는게 맞지 않을까 싶지만, 그런 상황이 흔하게 있을지는 잘 모르겠습니다.

선택적 필드 초기화는 피드백으로도 받았던 부분이기에, 가장 우선시하여 해결해야 할 과제로 보입니다.

다음 개선 사항은 나중으로 미뤄보도록 하겠습니다.

피드백 주신 분들 감사합니다.

코드는 아래 링크의 custom 패키지에서 만나볼 수 있습니다.

https://github.com/SongHeeJae/custom-builder-pattern

반응형

+ Recent posts