Java/Design Pattern

[디자인 패턴] 생성패턴 - 빌더 패턴 (Builder Patterns)

민돌v 2023. 2. 10. 19:26
728x90
(인프런) 코딩으로 학습하는 GoF의 디자인 패턴 - 백기선, 강의를 보고 정리한 글입니다.
코드는 GitHub 에 있습니다

 

#1. 객체 생성 관련 패턴

#2. 구조 관련 패턴

 

#3. 행동 관련 패턴

 

 

 


✔️ 빌더 패턴

  • 동일한 프로세스를 거쳐 다양한 구성의 인스턴스를 만드는 방법
  • 멤버변수가 많고, 생성자가 많아질 경우 복잡하고 파악하기 힘든 생성과정이 생기는 것이므로, 이를 순차적이고 선언적인 객체 생성 프로세스로 바꾸는 방법입니다.

빌더 패턴 구조

→ 빌더 패턴을 사용하면 (복잡한) 객체를 만드는 프로세스를 독립적으로 분리할 수 있다.


✔️ 빌더 패턴 구현 방법

  • 빌더 패턴 구현의 요점은, 메소드 체이닝 입니다.
  • 메소드 체이닝으로 엔티티의 데이터를 세팅해주는 빌더라는 인터페이스를 만들어 준 후 계속 스스로를 반환하고, 마지막에 해당 객체를 반환토록 하는 것입니다. 

 

객체

  • 아래와 같이 여러 멤버변수를 가지는 TourPlan 이라는 객체와 생성자가 존재합니다.
public class TourPlan {

    private String title;
    private int nights;
    private int days;
    private LocalDate startDate;
    private String whereToStay;
    private List<DetailPlan> plans;

    public TourPlan() {

    }

    public TourPlan(String title, int nights, int days, LocalDate startDate, String whereToStay,
        List<DetailPlan> plans) {
        this.title = title;
        this.nights = nights;
        this.days = days;
        this.startDate = startDate;
        this.whereToStay = whereToStay;
        this.plans = plans;
    }
}

 

  • 조금 비약해서 이 객체를 생성하고 데이터를 세팅해주면 아래으 사진처럼, 굉장히 복잡한 프로세스르 거쳐야합니다.

클라이언트 역할을 하는 APP 객체 - 매우 읽기 힘든 구조

⬇️

 

  • 이 구조를 이제 빌더 패턴을 적용해서 아래와 같은 구조로 변경해 보겠습니다.

  1. APP : 클라이언트의 역할을 하는 객체라고 보시면 됩니다.
  2. TourDirector : 빌더패턴을 적용할 TourPlanBuiler 를 목적에 맞게 미리 구현해둔 메소드의 집합을 가지는 객체입니다. (선택적이라고 생각, TestFixture 와 그 개념과 목적이 비슷)
  3. TourPlanBuilder : 빌더 패턴 적용한 interface
  4. DefaultTourPlanBuilder : TourPlanBuilder 의 구현체

 

TourPlanBuilder

  • 먼저 스스로를 반화하는 기능과 객체를 반환하는 메서드를 정의한 인터페이스로 빌더의 뼈대를 잡아야합니다.
  • 그리고 하위 구현체에서 데이터를 세팅해줍니다.
public interface TourPlanBuilder {

    //메서드 체이닝 사용 → 인터페이스에서 정의한 또 다른 기능을 사용하기 위해서
    TourPlanBuilder title(String title);
    TourPlanBuilder nightsAndDays(int nights, int days);
    TourPlanBuilder startDate(LocalDate localDate);
    TourPlanBuilder whereToStay(String whereToStay);
    TourPlanBuilder addPlan(int day, String plan);


    //getPlan 을 호출하기 전가지는 계속 체이닝 사용 (마지막 단계이기에 검증하기 좋은 위치)
    TourPlan getPlan();
}

public class DefaultTourBuilder implements TourPlanBuilder{

    private String title;
    private int nights;
    private int days;
    private LocalDate startDate;
    private String whereToStay;
    private List<DetailPlan> plans;

    @Override
    public TourPlanBuilder nightsAndDays(int nights, int days) {
        this.days = days;
        this.nights = nights;

        return this;
    }

    //생략

    @Override
    public TourPlanBuilder addPlan(int day, String plan) {
        if (this.plans == null) {
            this.plans = new ArrayList<>();
        }
        this.plans.add(new DetailPlan(day, plan));

        return this;
    }

    @Override
    public TourPlan getPlan() {
        return new TourPlan(title, nights, days, startDate, whereToStay, plans);
    }
}

 

⬇️

Client

  • 이제 빌더패턴을 적용해서 객체를 생성할 수 있습니다.
  • 빌더패턴을 적용하면 선언적이고, 순차적인 구조로 객체 생성의 프로세스를 작성할 수 있습니다.
public class App {

    public static void main(String[] args) {

        TourPlanBuilder builder = new DefaultTourBuilder();

        TourPlan plan = builder.title("칸쿤여행")
            .nightsAndDays(2, 3)
            .startDate(LocalDate.of(2020, 10, 2))
            .whereToStay("hotel")
            .addPlan(0, "저녁 식사")
            .addPlan(1, "아침 식사")
            .getPlan();

        TourPlan longBeachTrip = builder.title("롱비치")
            .startDate(LocalDate.now())
            .getPlan();
    }
}
  • 📌 또한 빌더패턴을 이용해서 초기 Builder 를 선언하는 부분을 이용한다면 파라미터를 이용해 강제적으로 데이터 세팅이 가능하도록 설계도 할 수 있습니다.
public class App {
    public static void main(String[] args) {
        
        TourPlan tourPlan = DefaultTourBuilder.Builder("title").getPlan();
    }
}


public class DefaultTourBuilder implements TourPlanBuilder{

    private String title;
    private int nights;
    private int days;
    private LocalDate startDate;
    private String whereToStay;
    private List<DetailPlan> plans;

    public static TourPlanBuilder Builder(final String title) {
        return new DefaultTourBuilder().title(title);
    }
 	
    //생략
}

 

데이터 세팅 강제

 

 


✔️ Lombok @Builder 가 생성하는 코드

  • Lombok 은 많은 개발자분들이 애용하는 어노테이션을 제공하는 라이브러리 입니다.
  • Lombok 이 제공하는 어노테이션 중 @Builder 가 존재하는데 이 빌더 어노테이션은 빌더패턴을 어떻게 적용하는지 살펴 보았습니다.

 

@Builder
public class TourPlan {

이렇게 겍체에 @Builder 를 붙이고 build를 돌리면, 컴파일 된 파일이 'build' 혹은 'target'폴더에 생성됩니다. 

 

Lombok @Builder Code

  • Lombok 은 리플렉션을 이용해서, 컴파일 시 해당 어노테이션이 등록된 위치에 맞춰, 정의된 기능을 주입시킵니다.
  • @Builder 가 적용된 객체는 위에서 살펴보았던 빌더패턴이 그대로 객체 내부 코드에 삽입된 걸 확인해 볼 수 있었습니다.
public class TourPlan {
    private String title;
    private int nights;
    private int days;
    private LocalDate startDate;
    private String whereToStay;
    private List<DetailPlan> plans;

    public TourPlan() {
    }

    public TourPlan(String title, int nights, int days, LocalDate startDate, String whereToStay, List<DetailPlan> plans) {
        this.title = title;
        this.nights = nights;
        this.days = days;
        this.startDate = startDate;
        this.whereToStay = whereToStay;
        this.plans = plans;
    }

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

    public static class TourPlanBuilder {
        private String title;
        private int nights;
        private int days;
        private LocalDate startDate;
        private String whereToStay;
        private List<DetailPlan> plans;

        TourPlanBuilder() {
        }

        public TourPlanBuilder title(final String title) {
            this.title = title;
            return this;
        }

        public TourPlanBuilder nights(final int nights) {
            this.nights = nights;
            return this;
        }

        public TourPlanBuilder days(final int days) {
            this.days = days;
            return this;
        }

        public TourPlanBuilder startDate(final LocalDate startDate) {
            this.startDate = startDate;
            return this;
        }

        public TourPlanBuilder whereToStay(final String whereToStay) {
            this.whereToStay = whereToStay;
            return this;
        }

        public TourPlanBuilder plans(final List<DetailPlan> plans) {
            this.plans = plans;
            return this;
        }

        public TourPlan build() {
            return new TourPlan(this.title, this.nights, this.days, this.startDate, this.whereToStay, this.plans);
        }

        public String toString() {
            return "TourPlan.TourPlanBuilder(title=" + this.title + ", nights=" + this.nights + ", days=" + this.days + ", startDate=" + this.startDate + ", whereToStay=" + this.whereToStay + ", plans=" + this.plans + ")";
        }
    }
}

 

 

그럼

끝!

 

 

 

 

반응형