본문 바로가기

스위프트

[Swift/디자인패턴] 빌더 패턴

빌더 패턴은 Creational Pettern 중 하나로

Director/Builder/Product 세 가지 요소로 구성되어있다.

 

빌더 패턴은

"특정 객체를 생성하는 과정에서 각 프로퍼티를 Set할 수 있는 인터페이스를 제공하여

동일한 프로덕트에 다른 프로퍼티 값을 상황에 따라 얼마던지 커스터마이징하여 쓸 수 있는"

패턴이다.

 

세 가지 요소로 구성된 빌더 패턴을 그림으로 그리자면 다음과 같다.

 

빌더 패턴은 다음 세가지 요소로 이뤄진다.

 

[1] Builder

빌더는 프로덕트를 생성하는 인터페이스를 제공한다.

프로덕트를 생성하는 빌더를 생성하여 비즈니스 로직에서 마지막에 build 메서드를 통해 프로덕트를 얻는다.

빌더는 상태의 보존을 유지하면서 특정 프로퍼티 변경에 따른 프로덕트 재생산일 때만 전역의 빌더 인스턴스 객체로 남는 것이 좋다.

 

그리고 주의해야할 것은

빌더 프로퍼티와 메서드 등은 실제 프로덕트의 프로퍼티와는 관계가 없다는 것이다.

빌더의 초기화 함수, 프로퍼티, 메서드는 결국 프로덕트를 만들어내는 인터페이스일 뿐이지

종속성 자체가 있는 것은 아니다.

 

아래 예제코드에서

빌더 내 대부분의 메서드는 프로덕트 프로퍼티의 setter 메서드이지만,

특정 메서드는 프로퍼티의 setter가 아닌, 모든 Bool형의 프로퍼티를 일괄처리하는 메서드가 있다.

(참조: setAllToggleProperty)

 

[2] Director

Builder로 동일한 비즈니스 로직 및 순서에 의해 빈번히 생성되는 객체 인스턴스가 존재한다면

디렉터에 해당 로직을 추가함으로써

디렉터의 접근으로만 동일한 객체 인스턴스를 적재적소에서 사용할 수 있다.

즉, 빌더가 요리에서 레시피의 한 줄이 될 수 있는 요소들을 담은 객체라면

디렉터는 일종에 프로덕트라는 음식을 만드는 레시피를 가지고 있는 객체이다.

 

[3] Product

빌더를 통해 생성될 인스턴스의 객체이다.

대게로 빌드 패턴을 통해 객체의 인스턴스를 생성하는 건

여튼 Product 내부의 세터 메서드를 통해 값을 변경하는 조작이 아닌

인터페이스가 추가되야하므로 소스량은 결국에 늘어날 수 있다.

그러므로, 프로퍼티의 개수가 많으면 빌더를 통해 인스턴스 조작을 손쉽게할 수 있으나

프로퍼티의 개수가 적다면 오히려 불필요한 접근자 두개가 생기는 것이니

절대 적거나 단순한 객체형태를 빌더로 구현하지는 말자.

(단순한 객체형태라면 오히려 객체의 생성을 서브 클래스에 맡기는 팩토리 메서드 패턴이 더 참조에 유리할 것으로 생각한다.)

 

 

import AVFoundation

// 1. 카메라 설정 값 정보를 가지는 클래스
final class CameraConfiguration: CustomStringConvertible {
    /// Type to manage media data.
    private let mediaType: AVMediaType

    /// Indicates physical positionof an AVCaptureDevice's hardware on the system.
    private let position: AVCaptureDevice.Position

    /// Type of a list of devices matching certain search criteria.
    private let captureDevice: AVCaptureDevice.DeviceType

    /// Indicates the session preset currently in use by the receiver.
    private let sessionPreset: AVCaptureSession.Preset

    /// The mode of the torch on the receiver's device, if it has one.
    private let torchMode: AVCaptureDevice.TorchMode

    /// Current status whether a user grants permission specific input device.
    private var authorizationStatus: AVAuthorizationStatus

    // MARK: - Variables

    private var makeupEnabled: Bool
    private var distortEnabled: Bool
    private var skinSmoothEnabled: Bool
    private var stickerEnabled: Bool
    private var availableMaxStickerCount: Int

    // MARK: - Initializer

    init(
        mediaType: AVMediaType,
        position: AVCaptureDevice.Position,
        captureDevice: AVCaptureDevice.DeviceType,
        sessionPreset: AVCaptureSession.Preset,
        torchMode: AVCaptureDevice.TorchMode,
        authorizationStatus: AVAuthorizationStatus,
        makeupEnabled: Bool,
        distortEnabled: Bool,
        skinSmoothEnabled: Bool,
        stickerEnabled: Bool,
        availableMaxStickerCount: Int
    ) {
        self.mediaType = mediaType
        self.position = position
        self.captureDevice = captureDevice
        self.sessionPreset = sessionPreset
        self.torchMode = torchMode
        self.authorizationStatus = authorizationStatus
        self.makeupEnabled = makeupEnabled
        self.distortEnabled = distortEnabled
        self.skinSmoothEnabled = skinSmoothEnabled
        self.stickerEnabled = stickerEnabled
        self.availableMaxStickerCount = availableMaxStickerCount
    }

    // MARK: - Internal Methods

    func isMakeupEnabled() -> Bool {
        makeupEnabled
    }

    func isDistortEnabled() -> Bool {
        distortEnabled
    }

    func isSkinSmoothEnabled() -> Bool {
        skinSmoothEnabled
    }

    func isStickerEnabled() -> Bool {
        stickerEnabled
    }

    func getAvailableMaxStickerCount() -> Int {
        availableMaxStickerCount
    }


    // MARK: - CustomStringConvertible Variable

    var description: String {
"""
MediaType: \(mediaType)
position: \(position)
captureDevice: \(captureDevice)
sessionPreset: \(sessionPreset)
torchMode: \(torchMode)
authorizationStatus: \(authorizationStatus)
makeupEnabled: \(makeupEnabled)
distortEnabled: \(distortEnabled)
skinSmoothEnabled: \(skinSmoothEnabled)
stickerEnabled: \(stickerEnabled)
availableMaxStickerCount: \(availableMaxStickerCount)
"""
    }
}



// 2. Product(여기서는 CameraConfiguration) 객체를 생성하는 빌더

final class CameraConfigurationBuilder {
    private let mediaType: AVMediaType
    private let position: AVCaptureDevice.Position
    private let captureDevice: AVCaptureDevice.DeviceType
    private let sessionPreset: AVCaptureSession.Preset
    private let torchMode: AVCaptureDevice.TorchMode = .auto
    private var authorizationStatus: AVAuthorizationStatus = .notDetermined
    private var makeupEnabled: Bool = false
    private var distortEnabled: Bool = false
    private var skinSmoothEnabled: Bool = false
    private var stickerEnabled: Bool = false
    private var availableMaxStickerCount = 5

    // 빌더에 initializer를 넣는다는 건 빌더로 생성되는 객체에 반드시 셋을 해야하는 프로퍼티가 존재할 때 강제할 수 있다.
    // MARK: - Initializer

    init(
        mediaType: AVMediaType,
        position: AVCaptureDevice.Position,
        captureDevice: AVCaptureDevice.DeviceType,
        sessionPreset: AVCaptureSession.Preset
    ) {
        self.mediaType = mediaType
        self.position = position
        self.captureDevice = captureDevice
        self.sessionPreset = sessionPreset
    }

    // MARK: - Internal Methods

    /// Product를 생성하는 빌드 메서드
    func build() -> CameraConfiguration {
        CameraConfiguration(
            mediaType: mediaType,
            position: position,
            captureDevice: captureDevice,
            sessionPreset: sessionPreset,
            torchMode: torchMode,
            authorizationStatus: authorizationStatus,
            makeupEnabled: makeupEnabled,
            distortEnabled: distortEnabled,
            skinSmoothEnabled: skinSmoothEnabled,
            stickerEnabled: stickerEnabled,
            availableMaxStickerCount: availableMaxStickerCount
        )
    }

    func setMakeupEnabled(_ enabled: Bool) {
        makeupEnabled = enabled
    }

    func setDistortEnabled(_ enabled: Bool) {
        distortEnabled = enabled
    }

    func setSkinSmoothEnabled(_ enabled: Bool) {
        skinSmoothEnabled = enabled
    }

    func setStickerEnabled(_ enabled: Bool) {
        stickerEnabled = enabled
    }

    func setAvailableMaxStickerCount(_ count: Int) {
        availableMaxStickerCount = count
    }

    // 이렇게 각 프로퍼티를 Set하는 메서드가 아닌, 특정 타입을 받거나 비즈니스 로직에 의해 프로덕트 프로퍼티가 변경될 수 있다.
    func setAllToggleProperty(_ enabled: Bool) {
        makeupEnabled = enabled
        distortEnabled = enabled
        skinSmoothEnabled = enabled
        stickerEnabled = enabled
    }
}



// 3. 빌더를 통해 Product를 생성하는 단순 예제
let builder = CameraConfigurationBuilder(
    mediaType: .video,
    position: .unspecified,
    captureDevice: .builtInDualCamera,
    sessionPreset: .high
)

let cameraConfig = builder.build()

print("===첫 번째 Config가 빌드된 시점의 빌더 상태===")
print(cameraConfig)

builder.setAllToggleProperty(true)
builder.setAvailableMaxStickerCount(10)

let anotherCameraConfig = builder.build()

print("\n===두 번째 Config가 빌드된 시점의 빌더 상태===")
print(anotherCameraConfig)



// 4. Director : 동일한 로직으로 같은 값을 가지는 객체를 빌드할 때 쓰는 편의 객체이다.
struct CameraConfigurationDirector {
    func createImageCameraConfiguration() -> CameraConfiguration {
        let builder = CameraConfigurationBuilder(
            mediaType: .video,
            position: .back,
            captureDevice: .builtInTripleCamera,
            sessionPreset: .hd1920x1080
        )

        builder.setAllToggleProperty(true)
        let config = builder.build()
        return config
    }

    func createVideoCameraConfiguration() -> CameraConfiguration {
        let builder = CameraConfigurationBuilder(
            mediaType: .video,
            position: .back,
            captureDevice: .builtInDualCamera,
            sessionPreset: .hd4K3840x2160
        )

        builder.setSkinSmoothEnabled(true)
        builder.setDistortEnabled(true)
        let config = builder.build()
        return config
    }
}



// 5. Director를 통한 Product 반환 예

let imageConfig = CameraConfigurationDirector().createImageCameraConfiguration()
let videoConfig = CameraConfigurationDirector().createVideoCameraConfiguration()

print("\n\n==============================================================\n\n")
print("===Director로 생성된 Image Configuration, 디렉터로 접근하여 언제든 객체 생성이 가능하다===")
print(imageConfig)

print("\n===Director로 생성된 Video Configuration, 디렉터로 접근하여 언제든 객체 생성이 가능하다===")
print(videoConfig)

 

===첫 번째 Config가 빌드된 시점의 빌더 상태===
MediaType: AVMediaType(_rawValue: vide)
position: AVCaptureDevicePosition
captureDevice: AVCaptureDeviceType(_rawValue: AVCaptureDeviceTypeBuiltInDualCamera)
sessionPreset: AVCaptureSessionPreset(_rawValue: AVCaptureSessionPresetHigh)
torchMode: AVCaptureTorchMode
authorizationStatus: AVAuthorizationStatus
makeupEnabled: false
distortEnabled: false
skinSmoothEnabled: false
stickerEnabled: false
availableMaxStickerCount: 5

===두 번째 Config가 빌드된 시점의 빌더 상태===
MediaType: AVMediaType(_rawValue: vide)
position: AVCaptureDevicePosition
captureDevice: AVCaptureDeviceType(_rawValue: AVCaptureDeviceTypeBuiltInDualCamera)
sessionPreset: AVCaptureSessionPreset(_rawValue: AVCaptureSessionPresetHigh)
torchMode: AVCaptureTorchMode
authorizationStatus: AVAuthorizationStatus
makeupEnabled: true
distortEnabled: true
skinSmoothEnabled: true
stickerEnabled: true
availableMaxStickerCount: 10


==============================================================


===Director로 생성된 Image Configuration, 디렉터로 접근하여 언제든 객체 생성이 가능하다===
MediaType: AVMediaType(_rawValue: vide)
position: AVCaptureDevicePosition
captureDevice: AVCaptureDeviceType(_rawValue: AVCaptureDeviceTypeBuiltInTripleCamera)
sessionPreset: AVCaptureSessionPreset(_rawValue: AVCaptureSessionPreset1920x1080)
torchMode: AVCaptureTorchMode
authorizationStatus: AVAuthorizationStatus
makeupEnabled: true
distortEnabled: true
skinSmoothEnabled: true
stickerEnabled: true
availableMaxStickerCount: 5

===Director로 생성된 Video Configuration, 디렉터로 접근하여 언제든 객체 생성이 가능하다===
MediaType: AVMediaType(_rawValue: vide)
position: AVCaptureDevicePosition
captureDevice: AVCaptureDeviceType(_rawValue: AVCaptureDeviceTypeBuiltInDualCamera)
sessionPreset: AVCaptureSessionPreset(_rawValue: AVCaptureSessionPreset3840x2160)
torchMode: AVCaptureTorchMode
authorizationStatus: AVAuthorizationStatus
makeupEnabled: false
distortEnabled: true
skinSmoothEnabled: true
stickerEnabled: false
availableMaxStickerCount: 5