본문 바로가기

유니티/디자인 패턴

[Unity] 빌더 패턴 (Builder Pattern)

반응형

이 포스팅의 예시는 필자가 임의로 작성한 코드임을 알려드립니다.

빌더 패턴이란?

이 디자인 패턴은 객체의 생성과정을 단계적으로 실행하여 복잡한 객체를 가독성 좋게 생성하는 기법이다.

 

빌더 패턴은 크게 두 가지 요소, 빌더(Builder)와 프로덕트(Product)로 이루어진다.

마치 공장에서 제품을 찍어내듯이 빌더가 객체를 원하는 기능을 덧붙이며 만들어낸다고 생각하면 된다.

 

빌더 패턴 구조


왜 사용할까?

장점

  1. 유연성 : 객체의 속성과 멤버변수를 변경하기 위해서는 함수를 확장하기만 하면 된다.
  2. 가독성 : 객체의 초기화를 더 알아보기 쉽게 변경할 수 있다. 과한 생성자나 함수의 매개변수를 줄인다.
  3. 재사용성 : 범용적인 빌더 클래스를 재사용해 다른 객체 생성에 응용하기 쉽다.

단점

  1. 복잡성 : 불필요하게 사용할 경우 오히려 코드가 더 복잡해질 수 있다.
  2. 런타임 오류 : 매개변수를 통한 입력과는 다르게, 설계를 잘못할 경우 필수 속성을 설정하지 않는 등 오류를 범할 가능성이 높아진다.

어떻게 사용할까?

객체가 많은 정보를 포함하고 있을 경우 빌더 패턴을 사용하면 좋다.

 

아래는 유니티엔진에서 로켓(Falcon)을 만들고 발사하는 과정을 빌더패턴을 이용해 작성한 예시이다.

다음 예제 코드를 살펴보자.

 

로켓 클래스

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Rocket : MonoBehaviour
{
    private Rigidbody powerEngine;
    private float speed;
    private float power;
    private float airResistance;
    private float fuelStorage;

    public Rocket AddWings(GameObject model, float speed)
    {
        Attach(model);
        this.speed += speed;
        return this;
    }

    public Rocket AddEngine(GameObject model, float power)
    {
        Attach(model);
        powerEngine = gameObject.AddComponent<Rigidbody>();
        this.power += power;
        return this;
    }

    public Rocket AddNosecone(GameObject model, float resistance, float speed)
    {
        Attach(model);
        this.speed += speed;
        this.airResistance += resistance;
        return this;
    }

    public Rocket AddThruster(GameObject model, float storage, float power)
    {
        Attach(model);
        this.fuelStorage += storage;
        this.power += power;
        return this;
    }

    private void Attach(GameObject parts)
    {
        Instantiate(parts).transform.parent = transform;
    }

    public void Launch()
    {
        StartCoroutine(MoveProcess());
    }

    IEnumerator MoveProcess()
    {
    	powerEngine.AddExplosionForce(10f, Random.onUnitSphere, 1f);
        while (fuelStorage >= 0)
        {
            fuelStorage -= Time.deltaTime;
            powerEngine.AddForce(power * speed * (airResistance + 1) * Time.deltaTime * Vector3.up);
            yield return null;
        }
    }
}

위 코드에서 함수들이 Rocket 클래스인 자신을 리턴한다는 것이 빌더 패턴의 핵심이다. 이를 통해 재귀함수처럼 연속적으로 코드를 이어나갈 수 있게 된다.

 

 

로켓 팩토리 클래스

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RocketFactory : MonoBehaviour
{
    public static RocketFactory instance;
    public Rocket rocketBase;
    public GameObject wings;
    public GameObject nosecone;
    public GameObject engine;
    public GameObject thruster;

    public static Rocket CreateBase()
    {
        if (instance == null)
            instance = FindFirstObjectByType<RocketFactory>();
        var rocketBase = Instantiate(instance.rocketBase);
        rocketBase.gameObject.AddComponent<BoxCollider>();
        return rocketBase;
    }

    public void FALCON()
    {
        var rocket = CreateBase().
            AddWings(wings, 20).
            AddEngine(engine, 45f).
            AddNosecone(nosecone, 0.05f, 20f).
            AddThruster(thruster, 0.45f, 3f);

        rocket.Launch();
    }

    public void Start()
    {
        FALCON();
    }
}

궁극적으로 이 로켓을 생성하고 발사하는 과정을 담은 클래스이다.

예시에서는 FALCON 하나만을 제작했지만, 위와 같은 방식으로 간편하게 다양한 객체를 생성할 수 있다.

무엇보다 가독성이 매우 좋다!


실행 영상

 

만약 빌더 패턴이 아닌 일반 생성자나 함수를 사용해 객체를 생성할 경우 다음과 같은 코드가 되는데

public Rocket(Rigidbody powerEngine, float speed, float power, float airResistance, float fuelStorage)
{
    this.powerEngine = powerEngine;
    this.speed = speed;
    this.power = power;
    this.airResistance = airResistance;
    this.fuelStorage = fuelStorage;
}

 

이 코드에 Model Mesh를 포함하는 로직이 추가된다면, 이는 사용하는 입장에서 코드를 읽고 관리하기 어려워진다.


정리

빌더 패턴은 객체가 많은 정보를 포함하고 있고, 초기화가 복잡한 경우 사용하면 좋은 디자인패턴이다. 이 외에 생성과정이 간단할 경우엔 이 패턴을 사용하지 않아도 된다.

반응형