제네릭 & 프로토콜
제네릭과 프로토콜을 결합하면 타입 안전성과 유연성을 모두 얻을 수 있습니다. 이는 RIBs 아키텍처나 고급 Swift 라이브러리에서 흔히 볼 수 있는 패턴입니다.
import UIKit
// 걷기 행동 프로토콜
protocol Walking {
func walk()
}
// 잡기 행동 프로토콜
protocol Catchable {
func grab()
}
// 영장류 정의 프로토콜
protocol Primate: AnyObject {
var feet: Walking { get }
}
// 제네릭 제약조건 추가
class Walker<WalkingType: Walking>: Primate {
var feet: Walking
init(walkingImplementation: WalkingType) {
self.feet = walkingImplementation
}
}
// 잡을 수 있는 영장류 프로토콜
protocol CatchablePrimate: Primate {
var hands: Catchable { get }
}
class TwoFeetWalker<WalkType: Walking, CatchType: Catchable>: Walker<WalkType>, CatchablePrimate {
var hands: Catchable
init(walkingImplementation: WalkType, catchingImplementation: CatchType) {
self.hands = catchingImplementation
super.init(walkingImplementation: walkingImplementation)
}
}
// 직립 보행 프로토콜 및 구현
protocol UpRightWalking: Walking {}
class UpRightWalker: UpRightWalking {
func walk() {
print("직립보행하는 자")
}
}
// 조립 가능한 잡기 프로토콜 및 구현
protocol AssemblyCatchable: Catchable {}
class AssemblyCatcher: AssemblyCatchable {
func grab() {
print("잡아서 조립 할 수 있는 자")
}
}
class Person: TwoFeetWalker<UpRightWalker, AssemblyCatcher> {
func introduce() {
print("나란사람이란 두다리로")
feet.walk()
print("그리고 두 팔로")
hands.grab()
}
}
let person = Person(
walkingImplementation: UpRightWalker(),
catchingImplementation: AssemblyCatcher()
)
person.introduce()
코드 구조 심층 분석
하이브리드 접근법(제네릭 + 프로토콜)의 예제를 더 자세히 살펴보겠습니다.
프로토콜 계층
protocol Walking {
func walk()
}
protocol Catchable {
func grab()
}
protocol Primate: AnyObject {
var foots: Walking { get }
}
protocol CatchablePrimate: Primate {
var hands: Catchable { get }
}
이 코드는 행동(behaviors)을 프로토콜로 정의하고, 계층적 관계를 표현합니다:
- Walking과 Catchable은 기본 행동을 속성으로 정의
- Primate(영장류)는 Walking 능력이 있는 객체를 정의
- CatchablePrimate는 걷고 잡을 수 있는 능력을 가진 객체(영장류)를 정의합니다.
제네릭 클래스 계층
class Walker<WalkingType: Walking>: Primate {
var feet: Walking
init(walkingImplementation: WalkingType) {
self.feet = walkingImplementation
}
}
class TwoFeetWalker<WalkType: Walking, CatchType: Catchable>: Walker<WalkType>, CatchablePrimate {
var hands: Catchable
init(walkingImplementation: WalkType, catchingImplementation: CatchType) {
self.hands = catchingImplementation
super.init(walkingImplementation: walkingImplementation)
}
}
이 부분에서는 제네릭 클래스 계층을 구성합니다:
- Walker<WalkingType>은 걷는 능력을 구현합니다.
- TwoFeetWalker<WalkType, CatchType>는 Walker를 상속받고, 잡는 능력을 추가
- 두 클래스 모두 제네릭 타입 매개변수를 사용하여 컴파일 타임에 타입 안전성을 보장
구체적인 구현 클래스
protocol UpRightWalking: Walking {}
class UpRightWalker: UpRightWalking {
func walk() {
print("직립보행하는 자")
}
}
protocol AssemblyCatchable: Catchable {}
class AssemblyCatcher: AssemblyCatchable {
func `catch`() {
print("잡아서 조립 할 수 있는 자")
}
}
class Person: TwoFeetWalker<UpRightWalker, AssemblyCatcher> {
func introduce() {
print("나란사람이란 두다리로")
self.foots.walk()
print("두팔로 ")
self.hands.catch()
}
}
여기서는 구체적인 구현을 제공합니다:
- UpRightWalker와 AssemblyCatcher는 각각 걷기와 잡기 기능을 구현
- Person은 TwoFeetWalker를 상속받고, 특정 타입의 걷기와 잡기 기능을 사용
어떨 때 사용하면 좋을까?
- 컴파일 타임 타입 안전성이 중요할 때
- 특정 타입에 특화된 기능을 제공해야 할 때
let person = Person(
walkingImplementation: UpRightWalker(),
catchingImplementation: AssemblyCatcher()
)
person.introduce()