[Swift] 프로토콜 지향 프로그램 - 2 (델리게이션 및 프로토콜 설계)

728x90

델리게이션 및 프로토콜 컴포지션에 대해 알아 보겠습니다.

 

델리게이션

델리게이션은 어느 한 타입의 인스턴스가 다른 인스턴스를 대신해서 동작하는 상황에 잘 맞는다.
동작을 위임하는 인스턴스는 델리게이트 인스턴스의 참조를 저장하고 있다가 어떠한 동작이 발생하면 델리게이팅 인스턴스는 계획된 함수를 수행하기 위해 델리게이트를 호출 합니다.

예제

<코딩관점>

프로토콜 구현

protocol DisplaynameDelegate {
    func displayName(name: String)
}

델리게이트를 사용하는 Person구조체

struct Person {
    var displaynameDelegate: DisplayNameDelegate
    
    var firstName = "" {
        didSet {
            displaynameDelegate.displayName(name: firstName)
        }
    }
    
    var lastName = "" {
        didSet {
            displaynameDelegate.displayName(name: lastName)
        }
    }
    
    init(displayNameDelegate: DisplayNameDelegate) {
        self.displaynameDelegate = displayNameDelegate
    }
    
    func getFullName() -> String {
        return "\(firstName) \(lastName)"
    }
}

DisplayNameDelegate 프로토콜을 따를 타입 생성

struct MyDisplayNameDelegate: DisplayNameDelegate {
    func displayName(name: String) {
        print("Name: \(name)")
    }
}

자 이제 한번 이름을 출력해볼까요?
델리게이트를 어떻게 사용하지는 봅시다.

protocol DisplayNameDelegate {
    func displayName(name: String)
}

struct Person {
    var displaynameDelegate: DisplayNameDelegate
    
    var firstName = "" {
        didSet {
            displaynameDelegate.displayName(name: firstName)
        }
    }
    
    var lastName = "" {
        didSet {
            displaynameDelegate.displayName(name: lastName)
        }
    }
    
    init(displayNameDelegate: DisplayNameDelegate) {
        self.displaynameDelegate = displayNameDelegate
    }
    
    func getFullName() -> String {
        return "\(firstName) \(lastName)"
    }
}

struct MyDisplayNameDelegate: DisplayNameDelegate {
    func displayName(name: String) {
        print("Name: \(name)")
    }
}

var displayDelegate = MyDisplayNameDelegate()
var person = Person(displayNameDelegate: displayDelegate)
person.firstName = "승진"
person.lastName = "김"

//출력
Name: 승진
Name: 김

 

 

<설계관점>

프로토콜지향 프로그래밍에서는 프로그램을 설계할 때 항상 프로토콜에서 시작하긴 하지만, 프로토콜은 어떠게 설계할 수 있을까요?
객체지향은 서브클래스를 위한 모든 기본적인 요구 사항을 포함하는 슈퍼클래스를 갖는 반면, 프로토콜 설계방식은 슈퍼클래스 대신 프로토콜을 사용하며, 이는 요구 사항을 더 큰 덩어리의 프로토콜이 아닌 작고 매우 구체적인 프로토콜로 나누기에 적절합니다.

 

로롯을 모델링하여 프로토콜 기반으로 간단한 설계를 해보죠
먼저 움직임에 대한 요구사항을 정의 하겠습니다.

protocol RobotMovement {
    func forward(speedPercent: Double)
    func reverse(speedPercent: Double)
    func left(speedPercent: Double)
    func right(speedPerent: Double)
    func stop()
}

 

3차원상에서 움직이게 하기 위한 프로토콜

protocol RobotMovementThreeDimensions: RobotMovement {
    func up(speedPercent: Double)
    func down(speedPercnet: Double)
}

 

센서 프로토콜

protocol Senser {
    var sensorType: String { get }
    var sensorName: String { get set }
    
    init(sensorName: String)
    func pollSensor()
}

 

센서 타입에 대한 프로토콜을 채택한 환경 센서에 대한 프로토콜'

protocol EnvironmentSensor: Sensor {
    func currentTemperature() -> Double
    func currentHumidity() -> Double
}

 

좀 더 많은 센서 타입을 만들어 봅시다.

protocol RangeSensor: Senser {
    func setRangeNotification(rangeCentimeter: Double, rangeNotification: () -> ())
    func currentRange() -> Double
}

protocol DisplaySensor: Senser {
    func displayMessage(message: String)
}

protocol wirelessSensor: Senser {
    func setMessageReceivedNotification(messageNotification: (String) -> Void)
    func messageSend(message: String)
}

 

이와같이 프로토콜 지향 설계에서 얻을 수 있는 장점에는 두 가지가 있다.

  1. 각 프로토콜은 특정 센서 타입에서 필요한 구체적인 요구 사항만들 포함한다.
  2. 프로토콜 컴포지션을 사용해 단일 타입이 다중 프로토콜을 따르게 할 수 있다.
protocol Robot {
    var name: String { get set }
    var robotMevement: RobotMovement { get set }
    var sensors: [Senser] { get }
    
    init(name: String, robotMovement: RobotMovement)
    func addSensor(sensor: Senser)
    func pollSensors()
}

Robot프로토콜을 따르는 세 개의 프로퍼티와 2개의 메소드를 정의 하고 있습니다.

 

슈퍼클래스 타입을 사용하는 것에만 익숙해 있다면 이 모든 프로토콜에 대해 생각한다는것이 혼란스러울지도 모릅니다.
좀 더 쉽게 이해하기 위해 다이어그램으로 나타내보겠습니다.

프로토콜을 사용해서 로봇의 컴포넌트에 대한 요구사항을 정의 해보았습니다.

이런식으로 프로토콜을 사용하여 매우 구체적인 요구 사항을 만드는 데 프로토콜을 사용할 수 있고, 그런 다음 프로토콜 계층 구조를 만들기 위해 프로토콜 상속과 프로토콜 컴포지션을 어떻게 사용하는지에 대해 알아 보았습니다.

 

 

참고

스위프트4 프로토콜지향 프로그래밍 3/e
에이콘