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

443자
5분

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

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

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

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

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

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

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

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

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

swift
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
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" 출력

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

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

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

swift
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 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]" 출력

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

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

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

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

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

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

YouTube 영상

채널 보기
class-validator 와 DTO | NestJS 가이드
Writer 펑터와 클라이슬리 카테고리 | 프로그래머를 위한 카테고리 이론
함수형 데이터 타입 | 프로그래머를 위한 카테고리 이론
클로드 섀넌이 들려주는 정보 이론 이야기
NestJS 필터 바인딩 - Method, Controller, Global Scope 비교 | NestJS 가이드
Product와 Coproduct가 Bifunctor인 이유 | 프로그래머를 위한 카테고리 이론
바이펑터란? | 프로그래머를 위한 카테고리 이론
매번 ValidationPipe 복붙하세요? NestJS 전역 파이프로 한 번에 해결하기 | NestJS 가이드