Lee's Grow up

[디자인패턴/Design Pattern] Builder 패턴 / 빌더 패턴 본문

PROGRAMMING/디자인패턴

[디자인패턴/Design Pattern] Builder 패턴 / 빌더 패턴

효기로그 2019. 12. 30. 15:46
반응형

관련 내용은 [자바 언어로 배우는 디자인 패턴 입문],[Head First Design Pattern],[Effective Java 3 개정판]의 내용을 참고해서 정리한 내용입니다.
잘못된 부분은 댓글로 피드백 부탁드립니다.

1. Builder 패턴이란?


객체의 생성 단계들을 캡슐화 하여 객체의 생성을 유연하게 해주는 패턴입니다. 즉 객체의 생성과정과 객체의 표현 방법을 분리합니다.

2. Builder 패턴의 등장인물


  • Builder(건축자)의 역할
    1. 인스턴스 생성을 위한 인터페이스(API)를 선언합니다.
  • ConcreteBuilder(구체적인 건축자)의 역할
    1. Builder 인터페이스를 구현하는 역할을 합니다.
  • Director(감독자)의 역할
    1. Builder 인터페이스(API)를 사용해 인스턴스를 사용합니다.
  • Productor(제품)의 역할
    1. 만들어질 제품의 속성과 기능을 가집니다.
Builder 패턴의 클래스 다이어그램

3. 예제


3-1. Builder 추상 클래스

제품을 필드로 가지며 객체 생성시 필요한 메소드를 선언하는 추상 클래스입니다.

public abstract class Builder {
    protected House house;

    public void createHouse() {
        house = new House();
    }

    public abstract void buildWalls();

    public abstract void buildDoors();

    public abstract void bouildRoof();

    public abstract void buildWindoes();

    public abstract House getHouse();
}
3-2. House 클래스

제품 클래스입니다. 생성될 제품의 속성과 기능을 가집니다.

public class House {
    private String roof;
    private String doors;
    private String windows;
    private String walls;

    public void setRoof(String roof) {
        this.roof = roof;
    }

    public void setDoors(String doors) {
        this.doors = doors;
    }

    public void setWindows(String windows) {
        this.windows = windows;
    }

    public void setWalls(String walls) {
        this.walls = walls;
    }

    @Override
    public String toString() {
        return "이집은 [" + roof + " 지붕과, " + walls + " 벽과, " + windows + "창문과 ," + doors + "문으로로 지어진 집입니다.]";
    }    
}
3-3. ConcreteHouseBuilder, WoodHouseBuilder 클래스

각각 Builder 추상 클래스의 기능을 재정의합니다.

public class ConcreteHouseBuilder extends Builder {

    @Override
    public void buildWalls() {
        house.setWalls("콘트리트");
    }

    @Override
    public void buildDoors() {
        house.setDoors("철제");
    }

    @Override
    public void bouildRoof() {
        house.setRoof("빨간 ");
    }

    @Override
    public void buildWindoes() {
        house.setWindows("일반 ");
    }

    @Override
    public House getHouse() {
        return house;
    }
}

public class WoodHouseBuilder extends Builder {

    @Override
    public void buildWalls() {
        house.setWalls("통나무");
    }

    ....

    @Override
    public House getHouse() {

        return house;
    }
}
3-4. Director 클래스

Builder을 속성으로 가지며, Builder 클래스의 메소드를 사용해 객체를 생성하고, 반환해줍니다.

public class Director {
    private Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }

    public void build() {
        builder.createHouse();
        builder.buildWalls();
        builder.buildWindoes();
        builder.buildDoors();
        builder.bouildRoof();
    }

    public House getHouse() {
        return builder.getHouse();
    }
}
3-5. Main 클래스
public class Main {
    public static void main(String[] args) {
        Builder concreteHouseBuilder = new ConcreteHouseBuilder();
        Director director = new Director(concreteHouseBuilder);
        director.build();

        House house1 = director.getHouse();
        System.out.println(house1);

        Builder woodHouseBuilder = new WoodHouseBuilder();
        director = new Director(woodHouseBuilder);
        director.build();

        House house2 = director.getHouse();
        System.out.println(house2);

    }
}
실행 결과
이집은 [빨간 철제 지붕과, 콘트리트 벽과, 일반 창문과 ,철제문으로 지어진 집입니다.]
이집은 [기와장  지붕과, 통나무 벽과, 나무 창문과 ,원목 나무문으로 지어진 집입니다.]
그렇다면 왜 사용할까?

이렇게 객체의 생성과정과 객체의 표현 방법을 분리하여 사용함으로써 다음과 같은 조합으로 객체 생성이 가능해집니다.

  콘크리트를 사용하는 Builder 나무를 사용하는 Builder
집을 만드는 Director 콘크리트 집 나무 집
학교를 만드를 Director 콘크리트 학교 나무 학교

사실 예제가 억지에 가깝지만.. 대충 이해한 내용을 코드로 작성해봤습니다.
이제 [Effective java]에서 소개하는 Builder Pattern을 소개하겠습니다.

4. Effective Builder 패턴


책에는 생성자 인자가 많을 경우 Builder 패턴 적용을 고려하라 라고 안내합니다. 즉 해당 책에서는 Builder 패턴을 객체 생성을 깔끔하고, 유연하게 할 수 있는 방법을 소개해줍니다.

4-1. 점층적 생성자 패턴
public class Member {
    private final String name ;
    private final Integer age;

    public Member(String name) {
        this(name, null);
    }

    public Member(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}

위 코드와 같은 클래스에서 새로운 필드인 주소, 이메일, 핸드폰 번호가 추가 되었을 경우 생성자를 어떻게 만들어야 할까? 생각만해도 머리가 아픕니다.. 이름과 이메일만 가지는 생성자, 이름과 핸드폰 번호만 가지는 생성자.......
또한, Member m = new Member("Lee",10,2)와 같이 생성자를 사용할 경우 각각의 인자가 무엇을 의미하는지 직관적으로 파악하기가 힘듭니다.
 그래서 등장한 방식이 자바빈 패턴입니다.

4-2 JavaBeans Pattern
public class Member {
    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public Integer getAge() {
        return age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

public class Main {
    public static void main(String[] args) {
        Member m = new Member();
        m.setName("Lee");
        m.setAge(20);
    }
}

해당 패턴을 통해 객체를 생성함으로써 불필요한 생성자가 필요가 없어졌고, 각각 메소드의 인자가 어떤 인자인지 메소드명을 통해서 파악하기 쉬워졌습니다.
그러나 setter을 통해 객체의 값이 변경 가능하기 때문에 immutable 클래스를 만들 수가 없고, 객체의 호출을 1번이 아닌 여러번을 하게 되는 단점이 있습니다

4-2 Effective java Builder Pattern

마지막으로 대망의 Effective java에서 소개하는 Builder Pattern의 방식입니다.

public class Member {

    private final String name;
    private final int age;
    private final String address;

    public static class Builder {
        // 필수 인자
        private final String name;

        private int age = 0;
        private String address = "";

        public Builder(String name) {
            this.name = name;
        }

        public Builder age(int age) {
            this.age = age;
            return this;
        }

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

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

    private Member(Builder builder) {
        name = builder.name;
        age = builder.age;
        address = builder.address;
    }

    @Override
    public String toString() {
        return "Member [name=" + name + ", age=" + age + ", address=" + address + "]";
    }
}

public class Main {
    public static void main(String[] args) {
        Member member = new Member
                .Builder("Lee") // 필수 인자
                .age(20)
                .address("Korea")
                .build(); // 객체 생성

        System.out.println(member);
    }
}

위 코드와 같이 객체를 생성할 수 있는 방식을 제공합니다. 해당 방식을 사용하면 아래와 같은 이점이 있습니다.

  • 각 인자를 파악하기 쉽다.
  • setter 메소드가 없으므로 변경 불가능 클래스 작성 가능
  • 객체의 일관성이 깨지지 않습니다. 한번만 호출
  • build() 메소드를 통해 값이 잘못 되었는지 검증 가능

Lombok이라는 라이브러리의 link @Builder 도 해당 방식의 빌더를 자동으로 생성해줍니다.

5. 관련 패턴


  • Template Method 패턴 : Builder 패턴에서는 Director이 Builder을 제어합니다. 반면 해당 패턴은 사위 클래스가 하위 클래스를 제어합니다. link
  • Composite 패턴 : Builder 패턴에 의해 만들어진 생성물은 해당 패턴이 되는 경우가 있습니다. link
  • Abstract Factory 패턴 : 동일하게 복잡한 인스턴스 생성을 담당합니다,
  • Facade 패턴 : Builder는 인스턴스 생성을 단순한 인터페이스(API)를 제공. 해당 패턴은 내부 모듈을 간단하게 사용하기 위한 인터페이스(API)를 제공합니다. link
반응형
Comments