TIL: 프로토콜 지향 프로그래밍(POP, Protocol-Oriented Programming)

Royce·2025년 3월 29일

Swift 문법

목록 보기
53/63

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

  • 프로토콜 지향 프로그래밍은 클래스 상속 대신 프로토콜과 프로토콜 확장(Extensions)을 이용하여 코드를 구조화하고 재사용성을 높이는 방법론이다
  • Swift는 구조체와 열거형도 프로토콜을 채택할 수 있기 때문에, 값 타입을 적극 활용하는 스타일을 권장한다

객체 지향 프로그래밍 (OOP)과의 차이점

객체 지향 프로그래밍(OOP)프로토콜 지향 프로그래밍(POP)
클래스 상속을 사용하여 기능을 확장한다프로토콜 채택 및 프로토콜 확장을 사용하여 기능을 확장한다
클래스 상속은단일 상속만 가능하며, 다중 상속을 지원하지 않는다여러 개의 프로토콜을 동시에 채택할 수 있어 다중 상속과 유사한 구성을 할 수 있다
클래스 상속은 상위 클래스의 메모리 구조를 강제로 따르도록 한다프로토콜은 메모리 구조에 대한 특정 요구 사항이 없기 때문에 유연하다
클래스 상속은 불필요한 속성과 메서드까지 상속받을 수 있다프로토콜은 필요한 기능만 채택할 수 있으므로 메모리 사용 측면에서 효율적이다
상속을 이용한 확장은 클래스(Reference Type)에서만 가능하다프로토콜은 모든 타입에서 사용할 수 있다(클래스, 구조체, 열거형)
런타임에 메서드를 동적으로 찾는다(동적 디스패치)컴파일 타임에 메서드를 정적으로 찾는다(정적 디스페치)
주로 클래스 타입을 사용한다값 타입(구조체, 열거형)을 적극 활용할 수 있다

객체 지향 프로그래밍 (OOP)의 단점 (상속의 문제점)

  1. 클래스 상속은 단일 상속만 가능하며 다중 상속을 지원하지 않는다
    • 하나의 클래스만 상속할 수 있기 때문에, 복잡한 기능을 조합하는 데 한계가 있다
  2. 불필요한 상속의 문제를 가진다
    • 상속받은 클래스는 기본적으로 상위 클래스의 모든 속성과 메서드를 물려받게 된다
    • 필요하지 않은 기능까지 포함될 수 있어 메모리 낭비가 발생할 수 있다
  3. 클래스 상속은 상위 클래스의 메모리 구조를 강제로 따르도록 한다
    • 클래스의 상속 구조를 따르다 보면, 특정 메모리 구조를 강제적으로 따라야 한다
    • 불필요하게 큰 메모리 구조를 가지거나, 비효율적인 메서드를 포함할 수 있다
  4. 클래스만 상속이 가능하며 참조 타입에서만 사용된다
    • 구조체와 열거형 같은 값 타입에서는 상속을 사용할 수 없다

프로토콜 지향 프로그래밍 (POP)의 장점 (OOP와 비교해서 우월한 점)

  1. 여러 개의 프로토콜을 동시에 채택할 수 있어 유연하게 기능을 조합할 수 있다
  2. 필요한 기능만 채택할 수 있으므로, 메모리 사용 측면에서 효율적이다 (@objc optional 지원)
  3. 클래스, 구조체, 열거형 등 모든 타입에서 사용할 수 있어 활용성이 높다
  4. 메모리 구조에 대한 제약이 없으므로, 필요한 기능만 구현할 수 있어 메모리 낭비가 없다
  5. 정적 디스패치 방식을 사용하여 컴파일 타임에 메서드를 결정하므로 성능이 뛰어나다 (특히 구조체 사용 시 효과적이다)
  6. 프로토콜은 타입으로 사용될 수 있어, 다양한 객체를 유연하게 다룰 수 있다 (let device: Device)
  7. 여러 프로토콜을 조합하여 유연하고 강력한 기능을 구성할 수 있다 (클래스 상속의 깊은 계층 구조 없이도 가능하다)
  8. 소급 적용 기능 (Retroactive Conformance)을 통해 기존 타입에도 프로토콜을 적용할 수 있다 (Int, String 등)

예제 코드 (POP의 장점을 활용한 코드)

// 기본 기능을 정의하는 프로토콜
protocol Drawable {
    func draw()   // 그림을 그리는 기능을 요구하는 메서드
}

protocol Scalable {
    func scale(by factor: Double)   // 크기를 조정하는 기능을 요구하는 메서드
}

// 두 개의 프로토콜을 조합하여 만든 상위 프로토콜 (다중 프로토콜 상속)
protocol AdvancedDrawable: Drawable, Scalable {}

// 기본 구현을 제공하는 확장 (모든 Drawable 타입에 적용)
extension Drawable {
    func draw() {
        print("도형을 그립니다")  // 기본 구현 제공 (확장을 사용하여 모든 Drawable에 제공)
    }
}

// 기본 구현을 제공하는 확장 (모든 Scalable 타입에 적용)
extension Scalable {
    func scale(by factor: Double) {
        print("크기를 \(factor)배로 조정합니다")  // 기본 구현 제공 (확장을 사용하여 모든 Scalable에 제공)
    }
}

// 기존 타입(Int)에 프로토콜을 소급 적용하여 기능 추가하기 (Retroactive Conformance)
extension Int: Drawable {
    func draw() {
        print("정수를 그립니다: \(self)")  // Int 타입도 Drawable 프로토콜을 채택하여 기능을 추가함
    }
}

// 새로운 타입 정의 (구조체도 프로토콜을 채택할 수 있음)
struct Rectangle: AdvancedDrawable {
    var width: Double
    var height: Double
    
    func area() -> Double {   // Rectangle 고유의 메서드 (프로토콜 요구사항과 무관)
        return width * height
    }
}

// 사용 예제
let rect = Rectangle(width: 10, height: 5)
rect.draw()            // 출력: 도형을 그립니다 (프로토콜 확장의 기본 구현 사용)
rect.scale(by: 2.0)     // 출력: 크기를 2.0배로 조정합니다 (프로토콜 확장의 기본 구현 사용)
print("면적: \(rect.area())")  // 출력: 면적: 50.0

let num: Int = 10
num.draw()              // 출력: 정수를 그립니다: 10 (소급 적용된 기능 사용)

요약

  • 다중 프로토콜 채택을 지원하여 유연하게 기능을 조합할 수 있는 특징이다
  • 프로토콜 확장은 기본 구현을 제공하여 코드 중복을 줄이고 유연성을 높일 수 있는 특징이다
  • 정적 디스패치를 활용하여 성능을 극대화할 수 있는 특징이다
  • 클래스 상속의 단점을 극복하고 모든 타입에서 사용 가능하도록 지원하는 특징이다
  • 소급 적용을 통해 기존 타입에 새로운 기능을 추가할 수 있는 특징이다
profile
iOS 개발자 지망생

0개의 댓글