디자인패턴

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

swimKind 2022. 8. 23. 22:57

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

 

 

 

*빌더 패턴

- 동일한 프로세스를 거쳐 다양한 구성의 인스턴스를 만드는 방법.

  • (복잡한) 객체를 만드는 프로세스를 독립적으로 분리할 수 있다.

 

 

 

 

- Product

package me.whiteship.designpatterns._01_creational_patterns._04_builder.practice;

import lombok.Getter;
import lombok.Setter;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

@Getter @Setter
public class Photograph {
    private String title;
    private String place;
    private LocalDate date;
    private String dress;
    private List<Suit> suits;

    public Photograph() {
        if (this.suits == null) {
            this.suits = new ArrayList<Suit>();
        }
    }

    public Photograph(String title, String place, LocalDate date, String dress, List<Suit> suits) {
        this.title = title;
        this.place = place;
        this.date = date;
        this.dress = dress;
        this.suits = suits;
    }

    public void addSuit(String color, String shape) {
        this.suits.add(new Suit(color, shape));
    }

    @Override
    public String toString() {
        return "Photograph{" +
                "title='" + title + '\'' +
                ", place='" + place + '\'' +
                ", date=" + date +
                ", dress='" + dress + '\'' +
                ", suits=" + suits +
                '}';
    }
}
package me.whiteship.designpatterns._01_creational_patterns._04_builder.practice;

import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class Suit {
    private String color;
    private String shape;

    public Suit(String color, String shape) {
        this.color = color;
        this.shape = shape;
    }

    @Override
    public String toString() {
        return "Suit{" +
                "color='" + color + '\'' +
                ", shape='" + shape + '\'' +
                '}';
    }
}

추후 빌더 패턴을 통해서 만들어낼 인스턴스의 Product(데이터)이다.

 

 

- Client

package me.whiteship.designpatterns._01_creational_patterns._04_builder.practice;

import java.time.LocalDate;

public class App {
    public static void main(String[] args) {
        Photograph firstPhotograph = new Photograph();
        firstPhotograph.setTitle("wedding photos");
        firstPhotograph.setDate(LocalDate.of(2022, 8, 20));
        firstPhotograph.setPlace("the studio");
        firstPhotograph.setDress("White flowing dress");
        firstPhotograph.addSuit("beige", "basic");
        firstPhotograph.addSuit("navy", "stripe");
        System.out.println("firstPhotograph = " + firstPhotograph);

        Photograph secondPhotograph = new Photograph();
        secondPhotograph.setTitle("wedding photos");
        secondPhotograph.setDate(LocalDate.of(2022, 8, 20));
        secondPhotograph.setPlace("the outside");
        secondPhotograph.setDress("Pink flowing dress");
        secondPhotograph.addSuit("brown", "basic");
        System.out.println("secondPhotograph = " + secondPhotograph);

        Photograph thirdPhotograph = new Photograph();
        thirdPhotograph.setTitle("wedding photos");
        thirdPhotograph.setDate(LocalDate.of(2022, 8, 20));
        thirdPhotograph.setPlace("the outside");
        thirdPhotograph.setDress("White slim dress");
        thirdPhotograph.addSuit("charcoal", "basic");
        System.out.println("thirdPhotograph = " + thirdPhotograph);
    }
}

빌더 패턴을 사용하지 않고 인스턴스를 만들게 되면 위와 같이 setter나 생성자를 통해서 인스턴스를 만들 수 있지만 입력해야 하는 값이 많아질수록 setter와 생성자는 입력해야 하는 값이 많아지고 또한 생성자는 값을 입력하지 않으려면 null 값이나 기본값을 입력해야 한다.

 

 

- Builder

package me.whiteship.designpatterns._01_creational_patterns._04_builder.practice;

import java.time.LocalDate;

public interface PhotographBuilder {
    PhotographBuilder title(String title);

    PhotographBuilder place(String place);

    PhotographBuilder startDate(LocalDate date);

    PhotographBuilder choiceDress(String dress);

    PhotographBuilder addSuit(String color, String shape);

    Photograph getPlan();
}

Builder 인터페이스를 만들고 필요한 메소드를 만든다. 각 메소드들은 Builder 인터페이스를 타입으로 갖게되어 Builder 인터페이스를 구현하는 구현체로 반환된다. 따라서 메소드 체인처럼 사용하고자 하는 메소드를 사용할 수 있다. 마지막에 Product를 반환하는 메소드를 만든다.

 

 

- ConcreteBuilder

package me.whiteship.designpatterns._01_creational_patterns._04_builder.practice;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

public class DefaultPhotographBuilder implements PhotographBuilder{

    private String title;
    private String place;
    private LocalDate date;
    private String dress;
    private List<Suit> suits;

    @Override
    public PhotographBuilder title(String title) {
        this.title = title;
        return this;
    }

    @Override
    public PhotographBuilder place(String place) {
        this.place = place;
        return this;
    }

    @Override
    public PhotographBuilder startDate(LocalDate date) {
        this.date = date;
        return this;
    }

    @Override
    public PhotographBuilder choiceDress(String dress) {
        this.dress = dress;
        return this;
    }

    @Override
    public PhotographBuilder addSuit(String color, String shape) {
        if (this.suits == null) {
            this.suits = new ArrayList<>();
        }
        this.suits.add(new Suit(color, shape));
        return this;
    }

    @Override
    public Photograph getPlan() {
        return new Photograph(this.title, this.place, this.date, this.dress, this.suits);
    }

    @Override
    public String toString() {
        return "DefaultPhotographBuilder{" +
                "title='" + title + '\'' +
                ", place='" + place + '\'' +
                ", date=" + date +
                ", dress='" + dress + '\'' +
                ", suits=" + suits +
                '}';
    }
}

추상화된 빌더 인터페이스를 구현하는 클래스이다. 각 메소드마다 Builder 인터페이스를 구현하는 구현체를 반환 해야하기 때문에 this를 리턴하고 마지막에 모든 값들을 반환하는 Product를 리턴한다.

 

 

- Client

package me.whiteship.designpatterns._01_creational_patterns._04_builder.practice;

import java.time.LocalDate;

public class BuilderApp {
    public static void main(String[] args) {
        PhotographBuilder photographBuilder = new DefaultPhotographBuilder();
        Photograph firstPhotograph = photographBuilder
                .title("wedding photos")
                .startDate(LocalDate.of(2022, 8, 20))
                .place("the studio")
                .choiceDress("White flowing dress")
                .addSuit("beige", "basic")
                .addSuit("navy", "stripe")
                .getPlan();
        System.out.println("firstPhotograph = " + firstPhotograph);

        Photograph secondPhotograph = photographBuilder
                .title("wedding photos")
                .startDate(LocalDate.of(2022, 8, 20))
                .place("the outside")
                .choiceDress("Pink flowing dress")
                .addSuit("brown", "basic")
                .getPlan();
        System.out.println("secondPhotograph = " + secondPhotograph);

        Photograph thirdPhotograph = photographBuilder
                .title("wedding photos")
                .startDate(LocalDate.of(2022, 8, 20))
                .place("the outside")
                .choiceDress("White slim dress")
                .addSuit("charcoal", "basic")
                .getPlan();
        System.out.println("thirdPhotograph = " + thirdPhotograph);

    }
}

빌더 패턴을 적용하고 인스턴스를 적용했다. 기존에 setter나 생성자를 사용하는 방식은 장황하게 만들어지는 데이터를 입력하는 부분에 수 많은 null값이나 기본 값을 넣었어야 한다. 빌더 패턴은 사용하고자 하는 부분만 입력하고 반환하면 된다.

 

 

- Director

package me.whiteship.designpatterns._01_creational_patterns._04_builder.practice;

import java.time.LocalDate;

public class PhotographDirector {

    private PhotographBuilder photographBuilder;

    public PhotographDirector(PhotographBuilder photographBuilder) {
        this.photographBuilder = photographBuilder;
    }

    public Photograph firstPhotograph() {
        return this.photographBuilder
                .title("wedding photos")
                .startDate(LocalDate.of(2022, 8, 20))
                .place("the studio")
                .choiceDress("White flowing dress")
                .addSuit("beige", "basic")
                .addSuit("navy", "stripe")
                .getPlan();
    }

    public Photograph secondPhotograph() {
        return this.photographBuilder
                .title("wedding photos")
                .startDate(LocalDate.of(2022, 8, 20))
                .place("the outside")
                .choiceDress("Pink flowing dress")
                .addSuit("brown", "basic")
                .getPlan();
    }

    public Photograph thirdPhotograph() {
        return this.photographBuilder
                .title("wedding photos")
                .startDate(LocalDate.of(2022, 8, 20))
                .place("the outside")
                .choiceDress("White slim dress")
                .addSuit("charcoal", "basic")
                .getPlan();
    }
}

또한 빌더 패턴에서 만들어지는 프로세스 등이 자주 반복이 된다면 미리 만들어져있는 부분을 Director에 넣어놓고 재사용할 수 있다.

 

 

- Client

package me.whiteship.designpatterns._01_creational_patterns._04_builder.practice;

public class DirectorApp {
    public static void main(String[] args) {
        PhotographDirector photographDirector = new PhotographDirector(new DefaultPhotographBuilder());
        Photograph FirstPhotograph = photographDirector.firstPhotograph();
        System.out.println("FirstPhotograph = " + FirstPhotograph);
        Photograph secondPhotograph = photographDirector.secondPhotograph();
        System.out.println("secondPhotograph = " + secondPhotograph);
        Photograph thirdPhotograph = photographDirector.thirdPhotograph();
        System.out.println("thirdPhotograph = " + thirdPhotograph);
    }
}

Client는 Builder가 아닌 Director를 통해서 자주 사용되거나 반복되는 Photograph에 관한 인스턴스를 구현 할 수 있다.

 

 

 

- 장점

  • 만들기 복잡한 객체를 순차적으로 만들 수 있다.
  • 복잡한 객체를 만드는 구체적인 과정을 숨길 수 있다.
  • 동일한 프로세스를 통해 각기 다르게 구성된 객체를 만들 수도 있다.
  • 불완전한 객체를 사용하지 못하도록 방지할 수 있다.

- 단점

  • 원하는 객체를 만들려면 빌더부터 만들어야 한다.
  • 구조가 복잡해 진다. (트레이드 오프)

 

빌더 패턴은 만드는 순서가 복잡한 인스턴스 같은 경우는 빌더 패턴의 메소드를 사용해야 하는 순서를 통해서만 만들어지도록 사용하는 방식을 강제할 수도 있다.

생성자 하나만으로 사용하게 되어 복잡해질 수 있는 부분을 분산할 수도 있다. 예를 들어, 입력 받은 값들을 검증하는 코드 등을 추가하는 등의 작업을 할 수 있다.

클라이언트 코드가 깔끔하게 만들 수 있고 같은 프로세스에 하위클래스 등을 만들어서 추가적이고 세부적인 것들이 추가 될 수 있다. (VIP용 등)

getPlan()과 같은 빌더패턴을 종결하는 메소드를 호출하기 전까진 메소드를 사용할 수 없기 때문에 에러를 방지할 수 있다.

 

 

 

https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4

 

코딩으로 학습하는 GoF의 디자인 패턴 - 인프런 | 강의

디자인 패턴을 알고 있다면 스프링 뿐 아니라 여러 다양한 기술 및 프로그래밍 언어도 보다 쉽게 학습할 수 있습니다. 또한, 보다 유연하고 재사용성이 뛰어난 객체 지향 소프트웨어를 개발할

www.inflearn.com