[TIL] Day22_Builder Pattern

오진선·2024년 2월 26일
0

TIL

목록 보기
13/29
post-thumbnail

Today I Learned

Builder Pattern

1. 정의

  • 복잡한 객체의 생성 과정과 표현 방법을 분리하여 다양한 구성의 인스턴스를 만드는 생성 패턴
  • 생성자에 들어갈 매개 변수를 메서드로 하나하나 받아들이고 마지막에 통합 빌드해서 객체를 생성
  • 클래스의 선택적 매개변수가 많은 상황에서 유용하게 사용

2. 역사

(1) 점층적 생성자 패턴(Telescoping Constructor Pattern)

class Hamburger {
    // 필수 매개변수
    private int bun;
    private int patty;

    // 선택 매개변수
    private int cheese;
    private int lettuce;
    private int tomato;
    private int bacon;

    public Hamburger(int bun, int patty, int cheese, int lettuce, int tomato, int bacon) {
        this.bun = bun;
        this.patty = patty;
        this.cheese = cheese;
        this.lettuce = lettuce;
        this.tomato = tomato;
        this.bacon = bacon;
    }

    public Hamburger(int bun, int patty, int cheese, int lettuce, int tomato) {
        this.bun = bun;
        this.patty = patty;
        this.cheese = cheese;
        this.lettuce = lettuce;
        this.tomato = tomato;
    }

}

public static void main(String[] args) {
    // 모든 재료가 있는 햄버거
    Hamburger hamburger1 = new Hamburger(2, 1, 2, 4, 6, 8);

    // 빵과 패티 베이컨만 있는 햄버거
    Hamburger hamburger3 = new Hamburger(2, 0, 0, 0, 0, 6);
}
  • 다양한 매개변수를 입력받아 인스턴스를 생성하고 싶을때 사용하던 생성자를 오버로딩 하는 방식
  • 인스턴스 변수의 순서를 지켜야 하며, 필드를 선택적으로 생략 불가능 -> 메서드의 수가 늘어나게 됨
  • 가독성과 유지보수 측면에서 문제가 있음

(2) 자바 빈(Java Beans) 패턴

class Hamburger {
    // 필수 매개변수
    private int bun;
    private int patty;

    // 선택 매개변수
    private int cheese;
    private int lettuce;
    private int tomato;
    private int bacon;
    
    public Hamburger() {}

    public void setBun(int bun) {
        this.bun = bun;
    }

    public void setPatty(int patty) {
        this.patty = patty;
    }

    public void setCheese(int cheese) {
        this.cheese = cheese;
    }

    public void setLettuce(int lettuce) {
        this.lettuce = lettuce;
    }

    public void setTomato(int tomato) {
        this.tomato = tomato;
    }

    public void setBacon(int bacon) {
        this.bacon = bacon;
    }
}

public static void main(String[] args) {
    // 모든 재료가 있는 햄버거
    Hamburger hamburger1 = new Hamburger();
    hamburger1.setBun(2);
    hamburger1.setPatty(1);
    hamburger1.setCheese(2);
    hamburger1.setLettuce(4);
    hamburger1.setTomato(6);
    hamburger1.setBacon(8);

    // 빵과 패티 베이컨만 있는 햄버거
    Hamburger hamburger3 = new Hamburger();
    hamburger3.setBun(2);
    hamburger2.setPatty(1);
    hamburger3.setBacon(8);
}
  • 가독성 문제점이 사라지고 선택적인 파라미터에 대해 해당되는 Setter 메서드를 호출함으로써 유연적으로 객체 생성이 가능
  • 객체 생성 시점에 모든 값들을 주입 하지 않아 일관성(consistency) 문제와 불변성(immutable) 문제가 나타남

(3) 빌더(Builder) 패턴

public static void main(String[] args) {

    // 생성자 방식
    Hamburger hamburger = new Hamburger(2, 3, 0, 3, 0, 0);

    // 빌더 방식
    Hamburger hamburger = new Hamburger.Builder(10)
        .bun(2)
        .patty(3)
        .lettuce(3)
        .build();
}
  • 별도의 Builder 클래스를 만들어 메서드를 통해 step-by-step 으로 값을 입력받은 후에 최종적으로 build() 메서드로 하나의 인스턴스를 생성하여 리턴하는 패턴
  • 빌더 클래스의 메서드를 체이닝(Chaining) 형태로 호출
  • 마지막에 build() 메서드를 통해 최종적으로 객체를 생성

3. Builder Pattern의 구조

1) Builder Class 만들기

// SturdentBuilder
class StudentBuilder {
    private int id;
    private String name;
    private String grade;
    private String phoneNumber;

    public StudentBuilder id(int id) {
        this.id = id;
        return this;
    }

    public StudentBuilder name(String name) {
        this.name = name;
        return this;
    }

    public StudentBuilder grade(String grade) {
        this.grade = grade;
        return this;
    }

    public StudentBuilder phoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
        return this;
    }
    
     public Student build() {
        return new Student(id, name, grade, phoneNumber); // Student 생성자 호출
    }
}

// Builder Class 실행
public static void main(String[] args) {

    Student student = new StudentBuilder()
                .id(2016120091)
                .name("임꺽정")
                .grade("Senior")
                .phoneNumber("010-5555-5555")
                .build();

    System.out.println(student);
}
  • Builder 클래스를 만들고 필드 멤버 구성을 만들고자 하는 Student 클래스 멤버 구성과 똑같이 구성
  • 각 맴버에대한 Setter 메서드를 구현
  • return this : 빌더 객체 자신을 리턴함으로써 메서드 호출 후 연속적으로 빌더 메서드들을 체이닝(Chaining)하여 호출 가능
  • 최종 Student 객체를 만들어주는 build 메서드를 구성
  • 빌더 클래스의 필드들을 Student 생성자의 인자에 넣어줌으로써 멤버 구성이 완료된 Student 인스턴스를 얻게 됨

(2) 빌더 패턴 네이밍 형식

  • 멤버이름() : 일반적으로 추천되는 형식
Student student = new StudentBuilder(2016120091)
        .name("홍길동")
        .grade("freshman")
        .phoneNumber("010-5555-5555")
        .build();
  • set멤버이름() : 기본 java 방식, setter와 구분되기 어렵다는 단점
Student student = new StudentBuilder(2016120091)
        .setName("홍길동")
        .setGrade("freshman")
        .setPhoneNumber("010-5555-5555")
        .build();
  • with멤버이름() : setter와 구분하기 위한 것, 빌더 지연 생성 방식에서 미리 빌더를 설정할때 사용하기도 함
Student student = new StudentBuilder(2016120091)
        .withName("홍길동")
        .withGrade("freshman")
        .withPhoneNumber("010-5555-5555")
        .build();

4. 장단점

(1) 장점

  • 객체 생성 과정을 일관된 프로세스로 표현
  • 디폴트 매개변수 생략을 간접적으로 지원
  • 필수 멤버와 선택적 멤버를 분리 가능
  • 객체 생성 단계를 지연할 수 있음
  • 초기화 검증을 멤버별로 분리
  • 멤버에 대한 변경 가능성 최소화를 추구

(2) 단점

  • 코드 복잡성 증가
  • 생성자 보다는 성능 저하
  • 지나친 빌더 남용은 금지

5. Spring에서의 Builder Pattern

(1) Lombok의 @builder 사용법

  • Setter 구현❌, final로 선언한 것과 같은 불변성을 확보
  • 생성자 레벨에 적용하기
public class Post {
	@Builder
    public post( ... ) {
    }
}
  • Class 레벨에 적용 -> Lombok의 @AllArgsConstructor와 같이 사용
@Builder
@AllArgsConstructor
public class Post {
	...
}
  • 초기값이 필요할 때에는 Builder.Default를 이용
    @Builder.Default
    private String grade = "VIP";

(2) Builder Pattern 활용

to Entity() : Dto -> Entity

  • entity를 생성하고, DTO에서 받아온 필드 값을 entity에 설정하여 반환
  • 주로 RequestDto에서 사용
public Trade toEntity(User user) {
        return Trade.builder() // Trade entity를 생성하기 위해 사용되는 빌더 패턴 시작 부분
                .title(this.title) // Trade entity의 title필드를 TradeDTO의 title값으로 설정
                .content(this.content)
                .latitude(this.latitude)
                .longitude(this.longitude)
                .price(this.price)
                .category(this.category)
                .user(user)
                .build();
}

of() : Entity -> Dto

  • entity를 조회한 결과를 DTO로 변환하여 컨트롤러에서 전달할 때 사용
  • entity에서 필요한 필드 값을 DTO에 설정하여 반환
  • 주로 ResponseDto에서 사용
public static TradeResponseDto of(Trade trade) {
        return TradeResponseDto.builder()
                .id(trade.getId())
                .title(trade.getTitle()) // TradeResponseDto의 title 필드를 Trade entity의 title 값으로 설정
                .content(trade.getContent())
                .username(trade.getUser().getUsername())
                .latitude(trade.getLatitude())
                .longitude(trade.getLongitude())
                .price(trade.getPrice())
                .category(String.valueOf(trade.getCategory()))
                .tradeLikeCount(trade.getTradeLikes().size())
                .tradeBookmarkList(trade.getTradeBookmarks().stream().map(bookmark -> bookmark.getTrade().getTitle()).toList())
                .tradeCommentList(trade.getTradeComments().stream().map(TradeCommentResponseDto::of).toList())
                .imageList(trade.getImage().stream().map(Image::getImageUrl).toList())
                .tradeChatroom(trade.getTradeChatRoom().getId())
                .build();
}

출처

https://inpa.tistory.com/entry/GOF-💠-빌더Builder-패턴-끝판왕-정리
https://dahlia15.tistory.com/78

profile
₍ ᐢ. ̫ .ᐢ ₎

0개의 댓글