객체를 생성하는 다양한 패턴

SeungHoon·2024년 6월 20일
0

Java

목록 보기
4/5

점층적 생성자 패턴

  • 아래는 우리가 흔히 알고 있는 생성자로 인스턴스를 생성하는 점층적 생성자 패턴이다.
Member member = new Member(1L, "chulsoo", "seoul", "...", "...", ......)
  • 클래스의 인스턴스를 만들때 매개변수가 아주 많아져서 길어지면 쓰기 불편해진다.
  • 더구나 매개변수의 순서도 정해져있어서 쓰기 어렵다.
  • 이렇게 기본 생성자만 두고 필드는 setter를 사용해 초기화하는 방식을 자바빈즈 패턴 (JavaBeans Pattern) 라고 한다.

자바빈즈 패턴 (JavaBeans Pattern)

public class Member {
    private Long id;
    private String name;

    private String address;

    public void setId(Long id) {
        this.id=id;
    }
    public void setName(String name) {
        this.name=name;
    }
    public void setAddress(String address) {
        this.address=address;
    }
}

<필드 값 세팅>

Member member = new Member();
member.setId(1L);
member.setName("chulsoo");
member.setAddress("seoul");

장점?

  • 코드가 길긴하지만 인스턴스를 만들기 쉽다.
  • 가독성이 좋다.

단점?

  • 객체 하나를 만들기 위해서 메서드(setter)를 여러 개 호출해야 한다.
  • 객체가 완전히 생성되기 전까지는 일관성(consistency)이 무너진 상태에 놓이게 된다. (모든 필드를 setter로 초기화 해줘야만 일관성이 유지된다)

빌더 패턴

public class Member {
    private Long id;
    private String name;
    private String address;

    public Member(Builder builder) {
        this.id=builder.id;
        this.name=builder.name;
        this.address=builder.address;

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

    public static class Builder{
        private Long id;
        private String name;
        private String address;

        public Builder id(Long id) {
            this.id=id;
            return this;
        }
        public Builder name(String name) {
            this.name=name;
            return this;
        }
        public Builder address(String address) {
            this.address=address;
            return this;
        }

        public Member build() {
            return new Member(this);
        }

    }

}

다음과 같이 세팅한 후 빌더를 사용하면

Member member = Member.builder()
      .id(1L)
      .name("chulsoo")
      .address("seoul")
      .build();

과 같이 가독성도 좋고 불변성도 챙길 수 있다.

근데 빌더도 코드가 너무 길어지면 빌더 선언 부분에서 가독성이 떨어질 수 있다. 그래서 다음과 같이 선언한다. @Builder 애노테이션을 사용하자.

public class Member {
    private Long id;
    private String name;
    private String address;
    @Builder
    public Member(String name, String address) {
        this.name=name;
        this.address=address;
    }
}

위 코드에서는 id는 builder로 만들지 않을 것이기 때문에 클래스가 아니라 생성자에서 @Builder를 달아두었다.

다음과 같이 유효성 검사도 해줄 수 있다.

public class Member {
    private Long id;
    private String name;
    private String address;
    
    @Builder
    public Member(String name, String address) {
        Assert.hasText(name, "name은 필수");
        Assert.hasText(address, "address은 필수");
        this.name=name;
        this.address=address;
    }
}

좀 더 나아가서

  • 솔직히 욕심이 난다. 객체를 생성하는 과정이 외부에 노출되는 것 자체를 줄이고 싶다면? 생성자를 private 으로 선언하고 정적 팩토리 메서드를 활용하는 것도 방법이 될 수 있다.
public class Member {
    private Long id;
    private String name;
    private String address;

    // 생성자 내부에 유효성 검증
    @Builder(access = AccessLevel.PRIVATE) // 외부에서 직접 builder 사용 못하게
    private Member(String name, String address) {
        Assert.hasText(name, "name은 필수");
        Assert.hasText(address, "address은 필수");
        this.name = name;
        this.address = address;
    }

    // 외부에서 호출 가능한 정적 팩토리 메서드
    public static Member create(String name, String address) {
        return Member.builder()
                     .name(name)
                     .address(address)
                     .build();
    }
}
Member member = Member.create("user1","서울시 용산구");
  • 다음과 같이 간단하게 만들 수 있다.
    • 물론 매개변수를 CreateMemberDTO 같은 DTO 을 사용해서 받는 것이 좋다고 생각한다. 예시에서는 빌더 패턴을 적용하기 위해 DTO 를 사용하지 않았다.
  • 그럼 이쯤에서 드는 생각이 있을 수 있다.

예시에서는 필드값이 2개밖에 없고, 어차피 클래스 내부에서 빌더를 사용할 거면 굳이 빌더를 사용해야 되나?

  • 사실 맞는 말이다. 필드가 여러 개가 아니라면 굳이 빌더를 사용할 필요는 없다. 그렇기에 상황에 맞게 빌더 패턴을 도입하는게 좋겠다.
profile
공유하며 성장하는 Spring 백엔드 취준생입니다

0개의 댓글