1️⃣ 의도
복잡한 객체를 생성하는 절차와 생성과정에서의 구체적인 표현 방식을 분리하는 패턴입니다.
동일한 절차내에서 서로다른 표현을 적용할 수 있는 인터페이스를 제공하는 것이 목표인 패턴입니다.
2️⃣ 동기
특정 포맷의 텍스트 파일을 다른 포맷의 파일로 변경하는 과정을 예시로 들 수 있습니다.
텍스 포맷 변경의 경우 특정 포맷을 읽을 수 있는 판독기가 파일을 토큰화 하고
토큰화 한 것을 다른 포맷으로 변경하는 방식으로 동작합니다.
이 경우 파일의 토큰 변환과정과 토큰의 포맷변형이 얽혀있다면 여러가지 포맷 변화를 지원하려는 경우 중복코드가 발생하거나 유지보수가 힘든 코드가 만들어질 수 있습니다.
여기서 파일을 토큰화 하는 것과 토큰들을 다른 포맷으로 변경하는 로직을 분리하여 문제를 해결할 수 있습니다.
"토큰들을 순차적으로 포맷으로 변형한다"라는 절차를 유지한채로 토큰 변형 알고리즘만 변경하는 것입니다.
3️⃣ 활용성
빌더 패턴은 다음 경우 사용합니다.
- 특정 복합 객체를 만들기 위한 조립법과 그 재료를 알 필요가 없는 경우
- 결과물이 달라도 조립과정은 동일한 경우
4️⃣ 패턴의 참여자
- 빌더 인터페이스: 복합 객체의 생성을 위한 인터페이스를 제공합니다.
- 예를들어 로봇을 만든다면 팔 생성, 머리 생성과 같은 인터페이스
- 구체적인 빌더: 빌더 인터페이스를 구현하고 완성된 복합 객체를 획득할 수 있는 메서드를 제공합니다.
- 디렉터: 빌더 인터페이스를 사용하여 복합 객체를 생성(합성)합니다.
- 해당 객체가 조립법을 다룹니다.
- 프로덕트: 생성할 복합 객체를 표현한다. 구체적인 빌더는 프로덕트의 내부 표현을 구축하는 역할을 합니다.
5️⃣ 참여자들의 협력 방법
- 클라이언트는 디렉터 객체를 생성합니다.
- 디렉터 객체로 구체적인 빌더를 전달합니다.
- 클라이언트 요청으로 디렉터는 조립을 시작하며 매 절차마다 빌더에게 알려줍니다.(빌더 인터페이스를 통해)
- 빌더는 자신만의 표현법으로 복합객체를 구축합니다.
- 사용자는 빌더에게 완성품을 요청하고 빌더는 프로덕트를 반환합니다.
6️⃣ 패턴 사용의 결과
- 복합객체를 만드는 과정에서 필요한 세부적인 타입들은 전부 구체적인 빌더로 격리됩니다.
새로운 프로덕트를 만들고 싶은 경우 새로운 빌더 객체를 만들어 주입하면됨으로 유연한 확장이 가능합니다. - 빌더는 재사용할 수 있습니다.
예를들어 앞서 언급한 텍스트 변환 문제에서 특정 포맷을 읽을 수 있는 판독기 변경되어도 토큰을 다른 포맷으로 변경하는 절차는 그대로 유지되기에 빌더를 재활용할 수 있습니다. - 복합객체를 만드는 과정에서 구체적인 합성 방법에 대해 전혀 논하지 않기에 조립 과정을 단순화 할 수 있습니다.
예를들어 로봇을 만드는 경우 "머리 장착", "손 장착" 이렇게 간단한 표현으로 절차를 서술할 수 있습니다.
구체적인 합성과정은 모두 빌더에게 위임되기 때문입니다.
결과적으로 조립 절차를 세분화할 수 있고 가독성 높은 코드를 만들 수 있습니다.
7️⃣ Swift로 구현
"Hello world"문자열을 만드는 빌더를 만들어 보겠습니다.
한국어와 영어를 사용해 언어별로 생성되도록 합니다.
빌더 인터페이스를 만듭니다.
protocol StringBuilder {
func addHello()
func addWorld()
func addSpace()
}
조립 절차를 가지는 디렉터를 만듭니다.
struct StringDirector {
static func createHelloWorld(builder: StringBuilder) {
builder.addHello()
builder.addSpace()
builder.addWorld()
}
}
다양한 표현을 가지는 구체적인 빌더를 만듭니다.
빌더별로 완성된 텍스트(프로덕트)를 얻을 수 있는 매서드를 각자 구현합니다.
final class KoreanStringBuilder: StringBuilder {
private var str: String = ""
func addHello() { str += "안녕" }
func addWorld() { str += "세상" }
func addSpace() { str += " " }
func getText() -> String { str }
}
final class EnglishStringBuilder: StringBuilder {
private var str: String = ""
func addHello() {
if str.isEmpty { str += "Hello"
} else { str += "hello" }
}
func addWorld() {
if str.isEmpty { str += "World"
} else { str += "world" }
}
func addSpace() { str += " " }
func getText() -> String { str }
}
영어의 경우 앞문자가 첫번째 문자인 경우 대문자로 표기하는 요소에 특화된 규칙을 가집니다.
하지만 디렉터의 경우 이러한 세부사항을 몰라도 됩니다!
let builder1 = KoreanStringBuilder()
StringDirector.createHelloWorld(builder: builder1)
let korText = builder1.getText()
print(korText) // 안녕 세상
let builder2 = EnglishStringBuilder()
StringDirector.createHelloWorld(builder: builder2)
let enText = builder2.getText()
print(enText) // Hello world
8️⃣ 느낀점
빌더 패턴과 추상 팩토리 패턴의 차이점
빌더 패턴은 추상 팩토리 패턴과 비슷해 보일 수 있지만, 서로 다른 점이 있습니다.
빌더 패턴은 하나의 복합 객체(프로덕트)를 완성하는 것에 중점을 둡니다.
예를 들어, 로봇이라는 프로덕트를 만들기 위해 필요한 부품과 조립 과정을 결정하는 역할을 합니다.
반면, 추상 팩토리 패턴은 시스템을 구성하는 객체들이 무엇인지 정의하는 데 더 초점을 맞춥니다.
예를 들어, 로봇을 구성하는 구체적인 부품 객체들의 집합(Kit)을 반환하는 역할을 합니다.
두 패턴은 기능이 완전히 다르다기보다는, 어떤 부분에 주목하느냐의 차이가 있다고 느꼈습니다.
RIBs아키텍쳐와 빌더 패턴
RIBs아키텍처는 빌더패턴을 사용합니다.
모듈을 구성하는 요소들의 생성과정과 상호작용을 추상화하고 필요한 프로덕트(Router)를 반환하는 구조로 동작합니다.
빌더 패턴의 결과물인 프로덕트들은 같은 서브 클래스, 타입일 필요는 없습니다.
RIBs의 경우 애플리케이션에 필요한 상호작용(화면 전환, 결과물간의 소통)을 위해 Router라는 추상화된 결과물을 반환하도록 하고 있습니다.
RIBs에서 Router는 프로덕트이자 디렉터의 역할을 수행합니다.
모듈의 구체적인 협력과정과 내부 구조를 모두 빌더에게 위임하기 때문에 빌더와 라우터를 통해서 시스템의 전체 구조를 쉽게 파악할 수 있습니다.
9️⃣ 총평
빌더 패턴은 프로덕트의 구체적인 구성 요소와 조립 방법을 캡슐화하여, 복잡한 협력 과정(조립 과정)을 명확하고 가시적으로 표현할 수 있도록 도와줍니다.
GitHub - J0onYEong/GOF-design-pattern: GOF디자인 패턴 실습코드 레포지토리입니다.
GOF디자인 패턴 실습코드 레포지토리입니다. Contribute to J0onYEong/GOF-design-pattern development by creating an account on GitHub.
github.com
'디자인 패턴' 카테고리의 다른 글
[디자인 패턴] 구조, Adapter pattern(적응자 패턴) with Swift (0) | 2025.04.13 |
---|---|
[디자인 패턴] 생성, Singleton pattern(단일체 패턴) with Swift (0) | 2025.04.10 |
[디자인 패턴] 생성, Prototype pattern(원형 패턴) with Swift (1) | 2025.04.10 |
[디자인 패턴] 생성, Factory method pattern(팩토리 매서드 패턴) with Swift (0) | 2025.04.09 |
[디자인 패턴] 생성, Abstract factory pattern(추상 팩토리 패턴) with Swift (0) | 2025.04.09 |