본문 바로가기
카테고리 없음

제네릭과 프로토콜을 활용하여 타입을 확장시키기

by zinozino 2025. 3. 6.
  •  

제네릭 & 프로토콜

제네릭과 프로토콜을 결합하면 타입 안전성과 유연성을 모두 얻을 수 있습니다. 이는 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()

결과