프로토콜 지향 프로그래밍

ellyheetov·2021년 5월 5일
0
post-thumbnail

스위프트는 프로토콜 지향 언어(Protocol-Oriented Language)이다.

스위프트는 대부분 구조체로 기본 타입이 구현되어있다. String, Int, Float, Date, URL 등등 익히 사용하고 있는 데이터 타입은 모두 구조체 이며, Array, Set, Dictionary 또한 구조체로 구현되어 있다.

구조체는 클래스와 달리 상속이 불가능하다. 상속도 되지 않는 구조체로 다양한 공통 기능을 가질 수 있는 이유는 Protocol과 Extension에 있다.

프로토콜이란?

프로토콜 이란 특정 기능에 필요한 메소드, 프로퍼티 등을 정의만 해 놓은 것을 말한다. (Java에서 Interface와 유사한 개념이다. 구현은 하지 않고 선언만 된 상태이다.)

이러한 프로토콜은 타입으로 사용이 가능하다.

프로토콜에 대한 자세한 설명은 이 글로 대체한다.

OOP의 단점

1. SuperClass에 종속적이다.

  • Subclassing을 하여 사용하기 위해서는 superclass의 코드를 알고 있어야 한다.
  • 서브클래스는 불필요한 변수화 함수를 상솓받을 수 밖에 없다.

2. Value Type을 사용할 수 없다.

  • 참조 타입은 참조 추적에 비용이 많이 발생한다. 하지만 상속을 사용하기 위해서는, 값 타입으로 정의해도 무방한 모델들을 굳이 참조 타입으로 정의해야 한다.

프로토콜 지향 프로그래밍이란?

필요한 부분만을 프로토콜로 분리하여 프로그래밍 하는 것을 말한다.

코드로 살펴보자.

예시

프로토콜 정의

  1. Tackable이라는 프로토콜의 정의한다.
protocol Talkable {
    var topic: String { get set }
    func talk(to: Self)
}
  1. Tackable 프로토콜을 채택한 구조체를 정의한다.
struct Person: Talkable {
    var topic: String
    var name: String

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

Person 구조체는 Talkable 프로토콜을 채택하고 있다. 프로토콜을 채택한다는 것은 프로토콜이 가지고 있는 내부 프로퍼티와 메소드를 반드시 구현해야한다는 것을 의미한다. Talkable을 채택한 Person 클래스는 talk(to:) 메소드를 사용할 수 있다.

  1. 프로토콜의 메소드 호출한다.

let elly = Person(topic: "Swift", name: "elly")
let coco = Person(topic: "Java", name: "Coco")


elly.talk(to: coco)
coco.talk(to: elly)

Person의 내부 코드를 알지 못 하더라도, Talkable을 채택하고 있으니 talk(to:) 메소드를 호출 하는 것이 가능하다.

  1. 여러개의 프로토콜을 추가한다.
protocol Eatable {
    func eat()
}
protocol Moveable {
    func run()
    func walk()
}
struct Person: Talkable, Eatable, Moveable {
    var topic: String
    var name: String

    func talk(to: Person) {}
    func eat(){}
    func run(){}
    func walk(){}
}

이렇게 필요한 기능은 프로토콜로 묶음으로써 기능의 모듈화가 가능해 진다.

Person의 여러 기능(?)중 특정한 함수만 필요하다면, 특정 프로토콜을 타입으로 사용할 수 있다. 예를들어, eat() 메소드만 필요하다면 Eatable 프로토콜을 타입으로 사용하는 것도 가능하다.

  1. Person 뿐만아니라 다른 타입도 Talkable 프로토콜을 채택할 수 있다.
struct Monkey: Talkable {
    var topic: String

    func talk(to: Monkey) {
        print("우끼끼 꺄꺄 \(topic)")
    }
} 

그런데 Talkable을 채택하는 구조체가 많아 질 수록 매번 talk(to:) 를 구현해야하는 불편함이 존재한다.

프로토콜 초기구현

이때 사용하는 것이 Extension이다. OOP의 수직적인 확장 구조가 아닌 수평적인 확장구조의 형태를 가진다.

  1. 프로토콜을 초기 구현한다.
    앞에서 정의한 프로토콜은 프로토콜은 구현이 되어있지 않아 채택한 하위 타입에서 구현해야했다. 하지만 Protocol Extension을 사용하여 초기 구현을 해 둘 수 있다.
// 익스텐션을 사용한 프로토콜 초기 구현
extension Talkable {
    func talk(to: Self) {
        print("\(to)! \(topic)")
    }
}
  1. 구현 하지 않고도 프로토콜의 메소드 호출한다.
struct Person: Talkable {
    var topic: String
    var name: String
}

struct Monkey: Talkable {
    var topic: String
}

let elly = Person(topic: "Swift", name: "elly")
let hana = Person(topic: "Internet", name: "hana")

elly.talk(to: hana)
hana.talk(to: elly) 

이렇게 하나의 프로토콜을 만들고, 초기 구현을 해둔다면 여러 타입에서 해당 기능을 사용하고 싶을 때 프로토콜을 채택하기만 하면되는 것이다.

만약, 프로토콜 초기 구현과 다른 동작을 원한다면, 프로토콜의 요구사항을 재정의 해주면 된다.

struct Monkey: Talkable {
    var topic: String
    func talk(to: Monkey) {
        print("\(to)! 우끼기기기끼기기")
    }
}

프로토콜지향 프로그래밍의 장점

  • SuperClass에 독립적
  • 값 타입 사용 가능
  • 값 타입 사용 시, 상속을 할 수 없으므로 매번 기능을 다시 구현해야 하는 한계를 극복
  • 기능의 모듈화 가능
  • 불필요한 API를 제외하고 정의한 API만 가져올 수 있음

참고

https://blog.yagom.net/531/
https://wlaxhrl.tistory.com/77

profile
 iOS Developer 좋아하는 것만 해도 부족한 시간

1개의 댓글