🔥 프로토콜 조합

614자
8분

때로는 하나의 타입이 여러 프로토콜을 동시에 준수하도록 요구하는 것이 유용할 수 있습니다. 이런 경우 프로토콜 조합을 사용하여 여러 프로토콜을 하나의 요구사항으로 결합할 수 있습니다. 프로토콜 조합은 조합에 포함된 모든 프로토콜의 요구사항을 결합한 임시 로컬 프로토콜을 정의한 것처럼 동작합니다. 프로토콜 조합 자체는 새로운 프로토콜 타입을 정의하지 않습니다.

프로토콜 조합은 SomeProtocol & AnotherProtocol과 같은 형태를 가집니다. 필요한 만큼의 프로토콜을 나열할 수 있으며, 앰퍼샌드(&)로 구분합니다. 프로토콜 목록 외에도 프로토콜 조합에는 하나의 클래스 타입을 포함할 수 있는데, 이를 통해 필수 상위 클래스를 지정할 수 있습니다.

다음은 NamedAged라는 두 개의 프로토콜을 함수 매개변수의 단일 프로토콜 조합 요구사항으로 결합하는 예제입니다:

protocol Named {
    var name: String { get }
}
 
protocol Aged {
    var age: Int { get }
}
 
struct Person: Named, Aged {
    var name: String
    var age: Int
}
 
func wishHappyBirthday(to celebrator: Named & Aged) {
    print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
 
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
// "Happy birthday, Malcolm, you're 21!"이 출력됩니다.
swift

이 예제에서 Named 프로토콜은 name이라는 가져올 수 있는 String 속성에 대한 단일 요구사항을 가집니다. Aged 프로토콜은 age라는 가져올 수 있는 Int 속성에 대한 단일 요구사항을 가집니다. 두 프로토콜 모두 Person이라는 구조체에서 채택되었습니다.

또한 이 예제에서는 wishHappyBirthday(to:) 함수를 정의합니다. celebrator 매개변수의 타입은 Named & Aged로, 이는 "NamedAged 프로토콜을 모두 준수하는 모든 타입"을 의미합니다. 함수에 전달되는 구체적인 타입은 중요하지 않으며, 필요한 두 프로토콜을 모두 준수하기만 하면 됩니다.

그 다음, 예제에서는 birthdayPerson이라는 새로운 Person 인스턴스를 생성하고 이를 wishHappyBirthday(to:) 함수에 전달합니다. Person은 두 프로토콜을 모두 준수하므로 이 호출은 유효하며, wishHappyBirthday(to:) 함수는 생일 축하 메시지를 출력할 수 있습니다.

다음은 이전 예제의 Named 프로토콜과 Location 클래스를 결합하는 예제입니다:

class Location {
    var latitude: Double
    var longitude: Double
 
    init(latitude: Double, longitude: Double) {
        self.latitude = latitude
        self.longitude = longitude
    }
}
 
class City: Location, Named {
    var name: String
 
    init(name: String, latitude: Double, longitude: Double) {
        self.name = name
        super.init(latitude: latitude, longitude: longitude)
    }
}
 
func beginConcert(in location: Location & Named) {
    print("Hello, \(location.name)!")
}
 
let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3)
beginConcert(in: seattle)
// "Hello, Seattle!"이 출력됩니다.
swift

beginConcert(in:) 함수는 Location & Named 타입의 매개변수를 받습니다. 이는 "Location의 하위 클래스이면서 Named 프로토콜을 준수하는 모든 타입"을 의미합니다. 이 경우 City는 두 요구사항을 모두 만족합니다.

birthdayPersonbeginConcert(in:) 함수에 전달하는 것은 유효하지 않은데, 그 이유는 PersonLocation의 하위 클래스가 아니기 때문입니다. 마찬가지로, Location의 하위 클래스를 만들었지만 Named 프로토콜을 준수하지 않는다면 해당 타입의 인스턴스로 beginConcert(in:)을 호출하는 것도 유효하지 않습니다.

프로토콜 조합을 사용하면 여러 프로토콜의 요구사항을 결합하여 더욱 구체적이고 제한적인 타입 요구사항을 표현할 수 있습니다. 이를 통해 코드의 안전성과 명확성을 높일 수 있죠.

다음은 프로토콜 조합의 활용 예시를 보여주는 코드입니다:

protocol Flyable {
    func fly()
}
 
protocol Swimmable {
    func swim()
}
 
struct Duck: Flyable, Swimmable {
    func fly() {
        print("오리가 날아갑니다!")
    }
 
    func swim() {
        print("오리가 수영합니다!")
    }
}
 
func performActions(on creature: Flyable & Swimmable) {
    creature.fly()
    creature.swim()
}
 
let donald = Duck()
performActions(on: donald)
// "오리가 날아갑니다!"와 "오리가 수영합니다!"가 출력됩니다.
swift

이 예제에서는 FlyableSwimmable이라는 두 개의 프로토콜을 정의하고, Duck 구조체에서 이들을 채택합니다. performActions(on:) 함수는 Flyable & Swimmable 타입의 매개변수를 받아 해당 객체의 fly()swim() 메서드를 호출합니다.

donald라는 Duck 인스턴스를 생성하고 performActions(on:) 함수에 전달하면, 오리가 날아가고 수영하는 동작이 수행됩니다.

이처럼 프로토콜 조합을 활용하면 특정 기능들의 조합을 요구하는 함수나 메서드를 유연하게 정의할 수 있습니다. 이는 코드의 재사용성을 높이고 타입 안정성을 보장하는 데 도움이 됩니다.

프로토콜 조합은 Swift의 강력한 기능 중 하나로, 프로토콜 지향 프로그래밍(Protocol-Oriented Programming)의 핵심 개념입니다. 프로토콜 조합을 적절히 활용하면 코드의 모듈화와 유연성을 크게 향상시킬 수 있습니다.

Swift에서 프로토콜은 매우 중요한 역할을 하며, 프로토콜 조합은 이를 더욱 확장하여 다양한 요구사항을 조합할 수 있게 해줍니다. 프로토콜 조합을 사용하면 클래스 상속 계층과는 별개로 타입에 대한 제약 조건을 명시할 수 있어 코드의 유연성과 재사용성이 높아집니다.

앞으로도 Swift 프로그래밍에서 프로토콜과 프로토콜 조합을 적극 활용하여 더욱 간결하고 표현력 있는 코드를 작성해 보시기 바랍니다!