본문 바로가기
Programming/프로그래밍 이론

[디자인 패턴] 빌더 패턴 (Builder pattern)

by SpiralMoon 2019. 3. 9.
반응형

빌더 패턴 (Builder pattern)

생성 패턴중 하나인 빌더 패턴에 대해 알아보자.

시리즈

2019.02.13 - [Programming/프로그래밍 이론] - [디자인 패턴] 싱글톤 패턴 (Singleton pattern)

2019.02.14 - [Programming/프로그래밍 이론] - [디자인 패턴] 추상 팩토리 패턴 (Abstract factory pattern)

2019.03.12 - [Programming/프로그래밍 이론] - [디자인 패턴] 복합체 패턴 (Composite pattern)

2019.03.20 - [Programming/프로그래밍 이론] - [디자인 패턴] 팩토리 메소드 패턴 (Factory method pattern)


빌더 패턴이란?

클래스 설계에서 생성 부분을 분리하여, 객체를 깔끔하고 유연하게 생성 부분만 담당하는 빌더를 고안한 패턴이다.

직접 구현할 일이 흔하지는 않지만, 프레임워크에서 제공해주면 생각보다 흔하게 쓴다.

특히, 안드로이드 개발자들은 빌더 패턴을 반드시 본 적이 있을 것이다.


예시

여러 물건을 소유한 사람을 클래스로 표현한다고 쳤을 때 아래 예시처럼 표현할 수 있다.

아래 예시는 C#을 기반으로 작성하였다.

 

// C#

// 클래스 정의
public class Human
{
    private int _money;
    private string _name;
    private float _height;
    private float _weight;

    public Human (int money, string name, float height, float weight)
    {
        _money = money;
        _name = name;
        _height = height;
        _weight = weight;
    }
}


// 사용 예시
var human = new Human(1000000000, "인간 1호", 180.0f, 75.0f);

 

위와 같은 Human 클래스가 있다고 쳤을 때 "빌더 패턴이 필요한가?" 라고 물으면 답은 "아니오"이다.

간단한 클래스 구조와 생성자가 있으며 생성자는 적당한 갯수의 파라미터를 요구하고 있다. 인스턴스화 할 때에도 가독성에 별 다른 문제점은 없어보인다.

 

public class Human
{
    private int _money;
    private string _name;
    private float _height;
    private float _weight;
    private string _address;
    private string _phone;
    private string _car;
    private string _job;
    private string _hobby;

    public Human (int money, string name, float height, float weight, string address, string phone, string car, string job, string hobby)
    {
        _money = money;
        _name = name;
        _height = height;
        _weight = weight;
        _address = address;
        _phone = phone;
        _car = car;
        _job = job;
        _hobby = hobby;
    }
}

 

멤버변수와 생성자가 요구하는 파라미터 갯수를 조금 늘려보았다. 4개에서 9개로 늘렸을 뿐인데 코드가 좌우로 늘어남에 따라 가독성이 매우 떨어졌다. 

 

객체 생성을 한 두번 정도면 코딩에 문제가 없겠지만, 여러번 할 경우에는 아무래도 코드가 굉장히 더러워질 것이다.

 

여기서부터는 빌더 패턴을 적용하여 위처럼 지저분한 생성파트를 분리할 것인지, 줄바꿈만 이용해 생성자의 가독성을 향상시킬 것인지 프로그래머가 결정해야한다.

 

그렇다면 위의 예제에 빌더 패턴을 적용해보자.

 

public class HumanBuilder
{
    private int _money;
    private string _name;
    private float _height;
    private float _weight;
    private string _address;
    private string _phone;
    private string _car;
    private string _job;
    private string _hobby;

    public HumanBuilder() { }

    public HumanBuilder SetMoney(int money)
    {
        _money = money;
        return this;
    }

    public HumanBuilder SetName(string name)
    {
        _name = name;
        return this;
    }

    public HumanBuilder SetHeight(float height)
    {
        _height = height;
        return this;
    }

    public HumanBuilder SetWeight(float weight)
    {
        _weight = weight;
        return this;
    }

    public HumanBuilder SetAddress(string address)
    {
        _address = address;
        return this;
    }

    public HumanBuilder SetPhone(string phone)
    {
        _phone = phone;
        return this;
    }

    public HumanBuilder SetCar(string car)
    {
        _car = car;
        return this;
    }

    public HumanBuilder SetJob(string job)
    {
        _job = job;
        return this;
    }

    public HumanBuilder SetHobby(string hoby)
    {
        _hobby = hoby;
        return this;
    }

    ///
    /// Builder가 수집한 파라미터를 이용해 인스턴스화 하는 함수.
    /// 
    public Human Build()
    {
        return new Human(_money, _name, _height, _weight, _address, _phone, _car, _job, _hobby);
    }
}

 

Human 클래스의 객체 생성을 담당하는 HumanBuilder 클래스를 작성하였다. 각 Set 함수가 빌더 객체를 반환해야 호출하는 쪽에서 체이닝이 가능하다.

 

var humanBuilder = new HumanBuilder();
var human = humanBuilder
    .SetMoney(1000000000)
    .SetName("Moon")
    .SetHeight(180.0f)
    .SetWeight(80f)
    .SetAddress("Korea **city")
    .SetPhone("010-0000-0000")
    .SetCar(null)
    .SetJob("Programmer")
    .SetHobby(null)
    .Build();

 

사용법은 위 코드와 같다. 빌더 객체를 만든 후, 값을 설정한 다음, .Build( ) 해주면 원하는 객체가 생성된다.

 

하지만 위에서 보여준 방법은 굳이 빌더를 이용하지 않아도 객체 생성을 할 수 있다는 문제점이 있다.

 

그러나, 빌더 패턴의 핵심은 반드시 빌더를 통해 객체 생성을 해야한다는 점이다.

 

public class Human
{
    private int _money;
    private string _name;
    private float _height;
    private float _weight;
    private string _address;
    private string _phone;
    private string _car;
    private string _job;
    private string _hobby;

    ///
    /// 빌더를 통해서 파라미터를 꺼내오는 생성자
    /// 
    private Human (HumanBuilder builder)
    {
        _money = builder.Money;
        _name = builder.Name;
        _height = builder.Height;
        _weight = builder.Weight;
        _address = builder.Address;
        _phone = builder.Phone;
        _car = builder.Car;
        _job = builder.Job;
        _hobby = builder.Hobby;
    }

    public class HumanBuilder
    {
        public int Money { get; private set; }
        public string Name { get; private set; }
        public float Height { get; private set; }
        public float Weight { get; private set; }
        public string Address { get; private set; }
        public string Phone { get; private set; }
        public string Car { get; private set; }
        public string Job { get; private set; }
        public string Hobby { get; private set; }

        public HumanBuilder() { }

        public HumanBuilder SetMoney(int money)
        {
            Money = money;
            return this;
        }

        public HumanBuilder SetName(string name)
        {
            _name = name;
            return this;
        }

        public HumanBuilder SetHeight(float height)
        {
            _height = height;
            return this;
        }

        public HumanBuilder SetWeight(float weight)
        {
            _weight = weight;
            return this;
        }

        public HumanBuilder SetAddress(string address)
        {
            _address = address;
            return this;
        }

        public HumanBuilder SetPhone(string phone)
        {
            _phone = phone;
            return this;
        }

        public HumanBuilder SetCar(string car)
        {
            _car = car;
            return this;
        }

        public HumanBuilder SetJob(string job)
        {
            _job = job;
            return this;
        }

        public HumanBuilder SetHobby(string hoby)
        {
            _hobby = hoby;
            return this;
        }

        ///
        /// Builder가 수집한 파라미터를 이용해 인스턴스화 하는 함수.
        /// 
        public Human Build()
        {
            return new Human(this);
        }
    }
}

 

Human 클래스의 생성자를 외부에서 사용할 수 없도록 public에서 private로 수정하였고, HumanBuilder를 통해야만 생성자가 작동하도록 수정하였다.

 

결론적으로 위 코드처럼 구현하면 "Human 클래스의 객체 생성은 반드시 HumanBuilder를 통해서만 할 수 있다"는 제약조건이 성립되며, 빌더 패턴이 적용되었다고 할 수 있다.

 

빌더 패턴의 조건은 다음과 같다.

 

  • 클래스의 생성은 빌더 클래스를 통해서만 할 수 있어야 한다.
  • 빌더 클래스에서 멤버 설정을 담당하는 함수들은 빌더 클래스 자체를 반환함으로써 체이닝을 지원해야한다.

장점

  • 빌더의 재사용성 : 클래스를 여러 번 인스턴스화 하려는 경우에 빌더를 재사용하여 객체를 찍어낼 수 있다. 빌더가 없었다면 생성자를 여러 번 호출 했을 것이다.
  • 객체 생성 영역 분리

단점

  • 의존성 : 클래스의 생성자나 멤버가 수정되면 빌더 클래스도 그에 호환되도록 수정해야한다.
반응형

댓글