빌더 패턴(Swift 적용)

브로디·2023년 1월 14일
0

디자인 패턴

목록 보기
1/1

정의

빌더 패턴이란 복합 객체의 생성 과정과 표현 방법을 분리하여 동일한 생성 절차에서 서로 다른 표현 결과를 만들 수 있게 하는 패턴

사용이유

복잡한 객체를 단계별로 초기화해야 하는 경우 많은 매개변수를 필요로 합니다. 이러한 매개변수는 거대한 생성자를 만들게 되고 모든 매개변수가 항상 필요하지는 않기 때문에 호출 부분의 코드가 매우 못생겨질 것입니다.

  • Swift에서는 대안으로 매개변수에 기본값을 주어 이 문제를 해결할 수 있습니다. 하지만 호출부에서 한 번에 모든 인자를 넘겨줘야하는 불편함은 없앨 수 없습니다. 그리고 호출부에서 객체를 만드는 세부사항을 감출 수 있기 때문에 빌더패턴이 필요한 것이죠.

빌더 패턴사용을 고려한 이유

  • AlertController를 사용해서 alert를 띄워주려는데 성공케이스와 실패케이스에 하드코딩하는 것이 보기 좋지 않았습니다. alert는 title, message, preferredStyle, action을 추가해줘야하기 때문에 이를 단계적으로 생성을 하고 서로 다른 객체를 구조화해서 사용하기위해 빌더 패턴을 적용해봤습니다.

빌더 패턴의 제안


  • 객체 생성을 일련의 단계들 buildWalls, buildDoors로 정리하고, 객체를 생성하려면 위 단계를 builder객체에 실행하면 됩니다.

디렉터

빌더 단계들에 대한 일련의 호출을 디렉터라는 별도의 클래스로 추출할 수 있습니다. 디렉터 클래스는 순서를, 빌더는 구현을 정의합니다.

  • 디렉터가 존재함으로써 클라이언트 코드에서 제품 생성의 세부 정보를 완전히 숨깁니다. 빌더로부터 결과를 얻기만 하면 되는 것이죠.

구조

  • 빌더: 모든 유형의 빌더들에 공통적인 제품 생성 단계들을 선언합니다.
  • 구상 빌더: 생성 단계들의 다양한 구현을 제공합니다. 이곳에서 공통 인터페이스를 따르지 않는 제품들도 생산할 수 있습니다.
  • 제품들: 결과로 나온 객체입니다.
  • 디렉터: 객체 생성 단계들을 호출하는 순서를 정의합니다.

빌더 패턴 구현(Swift)

Java에서의 Implement는 Swift에서 protocol의 개념으로 사용합니다.

프로토콜 정의

protocol Builder {
    func setServer(_ server: Server) -> Builder
    func setJob(_ job: Job) -> Builder
    func setGender(_ gender: Gender) -> Builder
}
  • 빌더클래스에서는 추상적인 메서드들을 정의합니다.

구상 빌더 정의

class CharactorBuilder: Builder {
    
    private var user = User()
    
    func reset() {
        user = User()
    }
    
    func setServer(_ server: Server) -> Builder {
        user.server = server
        return self
    }
    
    func setJob(_ job: Job) -> Builder {
        user.job = job
        return self
    }
    
    func setGender(_ gender: Gender) -> Builder {
        user.gender = gender
        return self
    }
    
    func buildUser() -> User {
        let user = self.user
        reset()
        return user
    }
}
  • 구상 빌더에서는 객체들의 내부 구현을 정의합니다.
  • return self 를 통해 호출부에서 체이닝 형태로 객체를 구현할 수 있습니다.
  • reset 함수는 실제 유저객체를 내보낼 때 실행함으로써 다음 빌더는 비어있는 상태로 만듭니다.
  • 리턴타입이 Builder라는 것을 유의해야 합니다.

디렉터 정의

class Director {
    
    private var builder: Builder?
    
    func update(builder: Builder) {
        self.builder = builder
    }
    
    func makeDefaultUser() {
        _ = builder?
            .setGender(.)
            .setServer(.아시아)
            .setJob(.전사)
    }
    
    func makeSpecialUser() {
        _ = builder?
            .setServer(.유럽)
            .setJob(.궁수)
    }
}
  • Director는 builder를 소유합니다.
  • 일련의 단계들을 정의하며 내부 구현은 빌더의 역할로 분리합니다.

유저 객체 정의

class User {
    var server: Server?
    var job: Job?
    var gender: Gender?
}

enum Server {
    case 아시아
    case 유럽
    case 남미
}

enum Job {
    case 전사
    case 마법사
    case 궁수
}

enum Gender {
    casecase}
  • 유저 객체를 정의합니다.
  • 옵셔널을 피하기 위해 struct를 정의할 경우 구상 빌더에서 유저 프로퍼티를 만들 때 초기값을 설정해야 하기 때문에 클래스로 정의하고 프로퍼티를 옵셔널로 주었습니다.

클라이언트 코드

let director = Director()
let builder = CharactorBuilder()
director.update(builder: builder)
director.makeDefaultUser()
print(builder.buildUser().gender)
director.makeSpecialUser()
print(builder.buildUser().gender)

// Optional(BuilderDesignPattern.Gender.남)
// nil
  • director와 builder를 정의한 후 director에 builder를 설정합니다.
  • 디렉터에게 설정된 빌더에 맞게 객체를 제조하도록 시킵니다.
  • 실제 객체를 가져오는 건 빌더에게 시킵니다. (구상 빌더에서 더 구체적인 정의가 있을 수 있기 때문)
  • makeSpecialUser함수를 실행했을 때 gender에 nil을 넣어주기 때문에 nil을 출력합니다. 만약 reset이 없었다면 이전에 설정해놓은 "남"이 출력될 것입니다.

장점

  • 객체들을 단계별로 생성하거나 생성단계들의 순서를 고려하지 않을 때 좋습니다.
  • 객체들의 다양한 표현을 만들 때 같은 생성 코드를 재사용할 수 있습니다.(위 예시경우에는 유저뿐만아니라 유저메뉴얼같은 객체를 사용할 때도 동일한 메서드를 사용할 수 있습니다)
  • 단일 책임 원칙으로 객체의 복잡한 비즈니스 로직에서 복잡한 생성 코드를 고립시킬 수 있습니다.

단점

  • 간단한 객체의 경우에도 여러 개의 새 클래스들을 생성하므로 복잡성이 증가합니다.

Reference

빌더 패턴
빌더 패턴
Builder pattern(swift)

profile
햅삐햅삐 데이

0개의 댓글