🔥 프로토콜 준수를 Extension으로 추가하기

443자
5분

Swift에서는 기존 타입을 확장하여 새로운 프로토콜을 채택하고 준수하도록 만들 수 있답니다. 심지어 기존 타입의 소스 코드에 접근할 수 없더라도 말이죠. Extension을 사용하면 기존 타입에 새로운 속성, 메서드, 서브스크립트를 추가할 수 있어서 프로토콜에서 요구하는 사항을 충족시킬 수 있게 됩니다. Extension에 대한 자세한 내용은 Extensions를 참고하시면 좋을 것 같아요.

예를 들어, TextRepresentable이라는 프로토콜은 텍스트로 표현될 수 있는 모든 타입이 구현할 수 있습니다. 이는 타입 자신에 대한 설명이거나 현재 상태를 텍스트로 나타낸 버전일 수 있죠.

protocol TextRepresentable {
    var textualDescription: String { get }
}
swift

앞서 살펴본 Dice 클래스는 TextRepresentable을 채택하고 준수하도록 확장할 수 있습니다.

extension Dice: TextRepresentable {
    var textualDescription: String {
        return "A \(sides)-sided dice"
    }
}
swift

이 Extension은 Dice가 원래 구현에서 제공한 것과 정확히 동일한 방식으로 새 프로토콜을 채택합니다. 프로토콜 이름은 타입 이름 뒤에 콜론으로 구분하여 제공되며, 프로토콜의 모든 요구 사항에 대한 구현은 Extension의 중괄호 내에 제공됩니다.

이제 모든 Dice 인스턴스를 TextRepresentable로 취급할 수 있게 되었네요.

let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
print(d12.textualDescription)
// "A 12-sided dice" 출력
swift

마찬가지로, SnakesAndLadders 게임 클래스도 TextRepresentable 프로토콜을 채택하고 준수하도록 확장할 수 있습니다.

extension SnakesAndLadders: TextRepresentable {
    var textualDescription: String {
        return "A game of Snakes and Ladders with \(finalSquare) squares"
    }
}
print(game.textualDescription)
// "A game of Snakes and Ladders with 25 squares" 출력
swift

프로토콜에 조건부로 준수하기

제네릭 타입은 타입의 제네릭 매개변수가 프로토콜을 준수할 때와 같이 특정 조건에서만 프로토콜의 요구 사항을 충족할 수 있습니다. 타입을 확장할 때 제약 조건을 나열하여 제네릭 타입이 조건부로 프로토콜을 준수하도록 만들 수 있어요. 채택하는 프로토콜의 이름 뒤에 제네릭 where 절을 작성하여 이러한 제약 조건을 작성하면 됩니다. 제네릭 where 절에 대한 자세한 내용은 Generic Where Clauses를 참조하세요.

다음 Extension은 TextRepresentable을 준수하는 타입의 요소를 저장할 때마다 Array 인스턴스가 TextRepresentable 프로토콜을 준수하도록 만듭니다.

extension Array: TextRepresentable where Element: TextRepresentable {
    var textualDescription: String {
        let itemsAsText = self.map { $0.textualDescription }
        return "[" + itemsAsText.joined(separator: ", ") + "]"
    }
}
let myDice = [d6, d12]
print(myDice.textualDescription)
// "[A 6-sided dice, A 12-sided dice]" 출력
swift

Extension으로 프로토콜 채택 선언하기

만약 어떤 타입이 이미 프로토콜의 모든 요구 사항을 준수하고 있지만, 아직 해당 프로토콜을 채택한다고 명시하지 않았다면, 빈 Extension으로 프로토콜을 채택하도록 만들 수 있습니다.

struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}
extension Hamster: TextRepresentable {}
swift

이제 Hamster의 인스턴스는 TextRepresentable이 필요한 곳이라면 어디에서든 사용될 수 있게 되었습니다.

let simonTheHamster = Hamster(name: "Simon")
let somethingTextRepresentable: TextRepresentable = simonTheHamster
print(somethingTextRepresentable.textualDescription)
// "A hamster named Simon" 출력
swift

이렇게 Extension을 활용하면 기존 타입의 기능을 확장하면서도 프로토콜 지향 프로그래밍의 장점을 누릴 수 있게 됩니다. 코드의 재사용성과 유연성이 높아지는 것이죠. 프로토콜과 Extension을 적절히 조합하여 사용한다면 더욱 강력하고 표현력 있는 코드를 작성할 수 있을 거예요.