@Builder

허준기·2024년 1월 31일
0

자바

목록 보기
9/9

자바로 코드를 짤 때 클래스와 객체를 다루게 된다

이 때 어떤 클래스에 대하여 객체를 생성할 때 생성자를 통해 값을 주입시켜줄 수 있다

Person junki = new Person("허준기", 25, "세종시");

이런식으로 Person이라는 클래스의 객체를 생성할 때 이름나이사는곳을 생성자를 통해 주입해줄 수 있다

그런데 이 때 만약 사는곳에 대한 정보를 넣기 싫어지면 Person 클래스 내에서 사는곳이 없는 생성자를 새로 만들어주어야 한다.

Person이라는 예시는 3개밖에 없지만 만약 생성자를 통해 주입해야 하는 변수가 늘어나고 같은 상황이 발생한다면 필요한 생성자의 수는 셀 수 없이 많아질 것이다.
이렇게 되면 코드가 복잡해지고 어떤 생성자를 썼는지 헷갈리게 된다.

이를 해결하기 위해 Builder 패턴을 사용할 수 있다.

빌더 패턴

우선 @Builder 패턴은 lombok 0.12.0 버전에서 처음 도입됐다

복잡한 객체들을 단계별로 생성할 수 있도록 하는 생성 디자인 패턴

@Builder 패턴을 사용해서 Person 클래스의 객체를 생성해보자

Person junki = Person.builder()
						.name("허준기")
                        .age(25)
                        .home("세종시")
                        .build();

이 코드는 맨 처음에 나온 모든 값을 넣어주는 방식이다. 이제 아까처럼 사는곳에 대한 정보를 넣기 싫어지면 다른 생성자를 만들 필요 없이 그냥 저 코드에서 .home("세종시") 만 빼주면 된다.

Person junki = Person.builder()
						.name("허준기")
                        .age(25)
                        .build();

이렇게 하면 새로운 생성자를 만들지 않고도 손 쉽게 새로운 객체를 만들 수 있다. 이때 아무것도 건들지 않은 home의 값에는 null이 들어가게 된다.

@Builder

간단한 예시를 봤으니 이제 사용하면 어떤식으로 돌아가는지 한 번 알아보자

현재 내가 만들고 있는 코드를 가져와보겠다

@Builder
    public Room(Integer roomId, Integer seats, Integer usedSeats, Integer remainSeats, List<Seat> seatList) {
        this.roomId = roomId;
        this.seats = seats;
        this.usedSeats = usedSeats;
        this.remainSeats = remainSeats;
        this.seatList = seatList;
    }

이런 코드가 있다
나는 처음에는 @Builder를 붙이면 생성자로 주입하는 변수들을 경우의 수를 따져서 그 수많은 생성자들을 만들어주는 줄 알았다..

그런데 코드를 까보니까 그런 방식이 아니었다!

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.example.studyroom.domain;

import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(
    name = "rooms"
)
public class Room {
    @Id
    @Column(
        name = "room_id"
    )
    private Integer roomId;
    @Column(
        name = "seats"
    )
    private Integer seats;
    @Column(
        name = "used_seats"
    )
    private Integer usedSeats;
    @Column(
        name = "remain_seats"
    )
    private Integer remainSeats;
    @OneToMany(
        cascade = {CascadeType.ALL}
    )
    @JoinColumn(
        name = "room_id"
    )
    @JsonManagedReference
    private List<Seat> seatList = new ArrayList();

    public Room() {
    }

    public Room(Integer roomId, Integer seats, Integer usedSeats, Integer remainSeats, List<Seat> seatList) {
        this.roomId = roomId;
        this.seats = seats;
        this.usedSeats = usedSeats;
        this.remainSeats = remainSeats;
        this.seatList = seatList;
    }

    public void usedSeats(Integer usedSeats) {
        this.usedSeats = usedSeats;
        this.remainSeats = this.seats - usedSeats;
    }

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

    public Integer getRoomId() {
        return this.roomId;
    }

    public Integer getSeats() {
        return this.seats;
    }

    public Integer getUsedSeats() {
        return this.usedSeats;
    }

    public Integer getRemainSeats() {
        return this.remainSeats;
    }

    public List<Seat> getSeatList() {
        return this.seatList;
    }

    public static class RoomBuilder {
        private Integer roomId;
        private Integer seats;
        private Integer usedSeats;
        private Integer remainSeats;
        private List<Seat> seatList;

        RoomBuilder() {
        }

        public RoomBuilder roomId(final Integer roomId) {
            this.roomId = roomId;
            return this;
        }

        public RoomBuilder seats(final Integer seats) {
            this.seats = seats;
            return this;
        }

        public RoomBuilder usedSeats(final Integer usedSeats) {
            this.usedSeats = usedSeats;
            return this;
        }

        public RoomBuilder remainSeats(final Integer remainSeats) {
            this.remainSeats = remainSeats;
            return this;
        }

        public RoomBuilder seatList(final List<Seat> seatList) {
            this.seatList = seatList;
            return this;
        }

        public Room build() {
            return new Room(this.roomId, this.seats, this.usedSeats, this.remainSeats, this.seatList);
        }

        public String toString() {
            return "Room.RoomBuilder(roomId=" + this.roomId + ", seats=" + this.seats + ", usedSeats=" + this.usedSeats + ", remainSeats=" + this.remainSeats + ", seatList=" + this.seatList + ")";
        }
    }
}

이런식으로 하나씩 떼어내서 주입시킬수 있도록 해주는거였다!!

역시 코드를 까보면 다 나오는 것 같다

암튼 이런식으로 돌아간다.

장점

  1. 필요한 데이터만 설정할 수 있다
  2. 위에서 예시를 든 것처럼 내가 원하는 값들만 넣은 객체를 생성해줄 수 있다
  3. 코드가 유연해질 수 있다
  4. 만약 새로운 변수를 추가해야된다고 생각해보자 기존의 생성자 주입 방식을 사용한다면 해당 생성자들에 새로운 변수들을 하나하나씩 넣어줘야 한다. 하지만 빌더 패턴을 사용하면 기존 코드에 영향을 주지 않아도 된다.
  5. 가독성을 높일 수 있다
  6. 빌더 패턴을 사용하면 변수가 많아져도 코드 가독성이 좋아진다. 생성자를 통해 주입을 하게 되면 안에 들어있는 값들이 어떤 의미인지 알기가 쉽지 않다. 하지만 빌더 패턴을 사용하면 .name("허준기") 처럼 이름이라는 변수에 "허준기"라는 값이 들어가는구나를 쉽게 알 수가 있다
  7. 변경 가능성을 최소화할 수 있다
  8. setter를 사용해서 불필요한 변경 가능성을 열어두는데 이렇게 되면 유지보수 시에 값이 할당된 지점을 찾기 힘들뿐만 아니라 불필요한 코드 리딩 등을 유발한다. 이 때 setter를 넣지 않고 빌더 패턴을 통해 값을 전달해주면 좋다

지금까지 Builder 패턴에 대해서 알아봤다.
친구가 코드를 짤 때 왜 이걸 쓰는지 알고 써야한다고 해서 알아보게 되었다. 이걸 쓰기 전에 왜 쓰는지에 대해서 생각을 해봤는데 아무리 생각해도 좋은점밖에는 생각이 안나서 찾아보니까 대부분이 좋은점인것 같다.

틀린부분이 있을수도 있으니 찾으면 댓글로 알려주세요!

profile
나는 허준기

1개의 댓글

comment-user-thumbnail
2024년 1월 31일

빌더패턴 정말 좋은거같네요 👍

답글 달기