POP(Protocol Oriented Programming)프로토콜 지향 프로그래밍이란?

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
참고한 블로그


POP(Protocol Oriented Programming)프로토콜 지향 프로그래밍

애플은 2015년 9월, 스위프트 버전 2.0을 발표하면서 지난 수십년간 사용된 개발 패턴이었던 객체지향 프로그래밍(OOP)가 아닌 프로토콜 지향 언어라고 발표했다고 한다. 이는 쉽게 말하자면 Class를 통한 OOP가 Struct와 Protocol을 통한 POP로 바뀌고 있다는 것을 의미한다. OOP의 핵심은 상속이다. 스위프트에서의 클래스 또한 상속이 가능하지만, 구조체나 열거형의 경우 상속이 불가능하다는 특징이 있다. 그렇기때문에 이전에는 스위프트 코드 대부분의 데이터 구조는 클래스를 통해 작성되었다. 그러나 스위프트에서 클래스와 같은 참조 타입은 단일 원본이 여기저기 참조되기 때문에 그리 권유되는 방법은 아니다. » iOS의 멀티쓰레드 참고 해당 블로그에서도 정리했듯이 클래스를 통해 기능적으로 하나의 원본을 여러군데에서 사용해야하는 경우도 있지만, 멀티쓰레드 환경에서는 한 원본을 두고 여러작업이 동시에 진행되게 되면 원본 데이터가 꼬일 가능성이 크기때문에 하나의 원본으로 작업해야할 필요가 없다면 가급적으로 값 타입인 구조체나 열거형을 사용하는 것을 권장한다.

그러다보니 현재 스위프트의 표준 라이브러리에서 타입과 관련된 것을 살펴보면 대부분이 구조체로 구현되어 있다. 그렇다면 상속도 되지 않는 구조체가 어떻게 다양한 공통 기능을 가질 수 있는 것일까?

Protocol Extension

참고할 블로그 - 프로토콜이란?

프로토콜은 이전에 정리했듯이 특정 역할을 수행하기 위한 메서드, 프로퍼티, 기타 요구사항등의 청사진이라고 할 수 있다. 예로들어 우리가 테이블뷰를 사용하려면 UITableViewDelegate 프로토콜을 채택하게 되는데, 해당 프로토콜을 채택하게 되면 우리가 필수적으로 구현해야하는 메서드가 존재한다. 즉, 우리가 프로토콜을 채택 한다면 반드시 프로토콜이 요구하는 기능을 구현하여 프로토콜을 반드시 준수 해야한다.

익스텐션은 기존 타입의 기능을 확장하는 것을 의미하는데, 한번 생각해보도록 하자.

만약 특정 프로토콜을 정의하고 여러 타입에서 프로토콜을 준수하게 만들어 타입마다 똑같은 메서드, 똑같은 프로퍼티, 똑같은 서브스크립트등을 구현해야한다고 생각해보자. 정말 많은 중독코드들이 발생하게 될 것이며, 더 나아가 유지,보수에 있어 큰 어려움을 겪게 될 것이다. 따라서 이때 필요한 것이 익스텐션과 프로토콜의 결합이다.

프로토콜 초기구현

프로토콜을 채택한 타입의 정의부에 프로토콜의 요구사항을 구현하지 않더라도 프로토콜의 익스텐션에 미리 프로토콜의 요구사항을 구현하는 것을 의미한다.

원래 프로토콜에는 기능을 수행하는 코드는 작성을 할 수 없는데, 이를 swift2 protocol extension에는 실제 값을 계산하고, 기능을 하는 메서드를 구현할 수 있게 되었다. 즉 일반 클래스처럼 프로퍼티와 메서드를 정의하는 것이 가능해짐을 의미한다. 즉 extension을 통해 protocol의 기능을 추가하는 것이 가능해지게 된 것이다.

protocol Talkable {
    var topic: String { get set }
    func talk(to: Self)
}

struct Person: Talkable {
    var topic: String
    var name: String

    func talk(to: Person) {
        print("\(topic)에 대해 \(to.name)에게 이야기합니다")
    }
}

이런식으로 Talkable이라는 프로토콜을 생성하고 Person은 이 프로토콜을 채택한다. 근데 Person 말고도 다른 타입에서도 채택하고 싶어진다면 그 타입에서도 Talkable 프로토콜이 요구하는 사항을 모두 구현해주어야 한다. 그런데 이때 프로토콜이 요구하는 사항을 미리 한꺼번에 구현해둔다면 중복되는 코드를 피할 수 있게 될 것이다.

protocol Talkable {
    var topic: String { get set }
    func talk(to: Self)
}

// 익스텐션을 사용한 프로토콜 초기 구현
extension Talkable {
    func talk(to: Self) {
        print("\(to)! \(topic)")
    }
}

이렇게 하나의 프로토콜을 만들어주고 초기구현을 해준다면 여러타입에서 해당 기능을 사용하고 싶을 때 해당 프로토콜만 채택해주면 된다. 만약 프로토콜의 초기구현과 다른 동작을 하고 싶다면, 해당 타입에 프로토콜의 요구사항을 재정의해주기만 하면 된다. 따라서 프로토콜 초기구현만 제대로 해놓는다면 여러 프로토콜을 그저 채택하기만 해도 그 타입에 기능이 추가될 수 있음을 의미한다.

프로토콜 지향 프로그래밍을 추구하는 이유

구조체, 클래스, 열거형 등 구조화된 타입 중에 상속은 클래스 타입에서만 가능하다.

클래스는 참조 타입이므로 참조 추적에 비용이 많이 발생한다. 비교적 비용이 적은 값 타입을 활용하고 싶어도, 상속을 할 수 없으므로 때마다 기능을 다시 구현해 주어야 했지만, 프로토콜 지향 프로그래밍은 그 한계를 없앴다. 즉 이를 통해 상속이 필요한 프로그램을 짤때에도 class가 아닌 struct, enum을 사용할 수 있도록 해준다.

기능의 모듈화가 더욱 명확해진다

클래스가 상속을 할 수 있도록 설계되어 있다고 하더라도 다중상속을 지원하는 언어는 많지 않다. 다중상속을 지원하지 않는다는 뜻은 하나의 상속체계에서 다른 상속체계에 속해있는 기능을 끌어다 쓸 수 없다는 뜻인데, 프로토콜 지향 프로그래밍은 기능을 프로토콜이라는 단위로 묶어 표현하고 초기 구현을 해 둘 수 있으니 상속이라는 한계점을 탈피할 수 있다. 따라서 OOP처럼 수직적인 확장구조가 아닌 수평적인 확장구조의 형태를 띄게 된다. 이는 곧 이것저것 제약없이 필요한 기능을 가져와 쓸 수 있음을 의미한다.

더 나아가 class는 모든 api에 접근이 가능한 반면 Protocol은 정의한 api만 가져오게 된다. 이는 곧 더 가볍고 보안성 높게 코드를 짤 수 있음을 의미한다.