Protocol Oriented Programming, POP

ios dev·2021년 9월 23일
0

Protocol Oriented Programming, POP?

Apple2015년 9월, WWDC에서 Swift 2.0을 발표하면서 Swift프로토콜 지향 언어(Protocol-Oriented Language)라고 발표했다.

객체 지향 프로그래밍(Object Oriented Programming, OOP) 패러다임에 기반을 둔 언어들은 class상속을 사용해 해당 타입이 가지는 공통적인 기능모듈화해 구현한다.

하지만 class와 같은 참조 타입의 경우 다중 스레드 환경에서 사용할 때 잘못된 참조로 인해 원본 데이터변경될 수 있는 위험이 있다.

실제 Swift표준 라이브러리에서 타입과 관련된 것들을 살펴보면 대부분 구조체로 구현되어 있고 다양한 이유로 Apple은 특정 상황을 제외하고 구조체 사용을 권장한다.

하지만 struct 혹은 enum의 경우 상속이 불가능한데, 어떻게 다양한 공통 기능모듈화할 수 있을까? 이에 대한 해답은 protocolextension에 있다.


Protocol?

protocol은 특정 작업이나 기능에 적합한 method, property 그리고 그 외 ‘필수 조건 (requirements)’ 들의 '밑바탕 설계(blueprint)'를 정의한다.

그런 다음 class, struct 또는 enumprotocol의 요구 사항을 실제 구현하기 위해 protocol채택할 수 있다. protocol의 요구 사항을 충족하는 모든 유형은 'protocol을 준수한다.'라고 한다.


Protocol Extension

protocol을 채택한 ‘준수 타입 (conforming types)’이 구현해야 하는 '필수 조건'을 지정하는 것 외에도 protocol'extension'하여 '필수 조건' 중 일부를 구현하거나 '준수 타입'이 활용할 수 있는 추가 기능을 구현할 수 있다.


Protocol Default Implememtation, 프로토콜 초기구현

Protocol Default Implememtationprotocolextension의 결합으로 만들어진다.

protocol 정의 후 여러 타입에서 이 protocol을 사용하게 될 경우, 우리는 각 타입마다 똑같은 property, 똑같은 method, 똑같은 subscript를 구현하게 된다. 이는 많은 코드 중복을 발생시키고 유지보수 또한 힘들게 만든다.

이와 같은 단점을 극복하기 위해 필요한 기능이 Protocol Default Implememtation이다. protocol을 채택한 준수 타입에서 protocol의 모든 요구사항을 일일이 구현할 필요없이 extension을 통해 미리 protocol의 요구사항을 구현할 수 있다.

Example 1.

여기 'Talkable'이라는 protocol이 있다. 'Talkable' protocol이 하나의 타입에서만 채택된다면 큰 문제가 없겠지만 아래와 같이 'Korean''American' 타입 모두 'Talkable' protocol 채택하게 된다면 두 타입 모두 동일한 요구사항을 각각 구현해야 할 것이다.

// protocol 정의
protocol Talkable {
    var language: String { get set }
    
    func getLanguage()
}

// 타입 1
struct Korean: Talkable {
    var language: String
    
    func getLanguage() {
        print("---> I speak \(language)")
    }
}

// 타입 2
struct American: Talkable {
    var language: String
    
    func getLanguage() {
        print("---> I speak \(language)")
    }
}

하지만 아래와 같이 원하는 기능을 초기 구현(Protocol Default Implememtation) 한다면 protocol을 채택하기만 해도 해당 기능을 사용할 수 있다.
(만약 protocol 초기 구현과 다른 동작을 하고 싶다면, 해당 동작을 구현할 타입에 protocol 요구사항을 재정의하면 된다.)

// protocol 정의
protocol Talkable {
    var language: String { get set }
    
    func getLanguage()
}

// protocol 초기 구현
extension Talkable {
    func getLanguage() {
        print("---> I speak \(language)")
    }
}
    
// 타입 1
struct Korean: Talkable {
    var language: String
}

// 타입 2
struct American: Talkable {
    var language: String
}

let korean = Korean(language: "korean")
korean.getLanguage() // ---> I speak korean

let american = American(language: "english")
american.getLanguage() // ---> I speak english

Example 2.

여기에 Genericassociatedtype을 이용하면 더욱 유연한 protocol 구현이 가능하다.

// protocol 정의
protocol Stackable {
    // 프로토콜을 채택하는 데이터 타입이 Generic일 경우 사용
    associatedtype item
     
    var items: [item] { get set }
    
    mutating func add(item: item)
}

// protocol 초기 구현
extension Stackable {
    mutating func add(item: item) {
        items.append(item)
    }
}

// struct
struct Stack<T>: Stackable {
    var items: [T]
    
    typealias item = T
}

var intStack = Stack<Int>(items: [1, 2, 3])
intStack.add(item: 4)
intStack.items // ---> [1, 2, 3, 4]

var strStack = Stack<String>(items: ["하나", "둘", "셋"])
strStack.add(item: "넷")
strStack.items // ---> ["하나", "둘", "셋", "넷"]

결론 - Protocol Oriented Programming을 추구하는 이유

  • 상속은 class와 같은 참조 타입에서만 가능한데 참조 추적에는 많은 비용이 발생한다. 하지만 POP를 통해 값 타입을 활용할 수 있게 되었고 상속과 같은 기능을 비교적은 비용으로 제공할 수 있게 되었다.
  • 상속의 경우 다중 상속을 지원하지 않는 수직적인 구조를 갖는다. 하지만 POP는 수평적인 구조로 기능을 확장시킬 수 있고 이로 인해 기능의 모듈화를 더욱 명확히 할 수 있다.




cf.
https://docs.swift.org/swift-book/LanguageGuide/Protocols.html
https://xho95.github.io/swift/language/grammar/protocol/2016/03/03/Protocols.html#fn:blueprint
https://blog.yagom.net/531/
https://duwjdtn11.tistory.com/618

0개의 댓글