Swift에서 프로토콜 지향 프로그래밍(POP)에 대해서 알아보자

오늘은 한번 정리하고 싶었던 주제인 프로토콜 지향 프로그래밍에 대해서 정리하는 시간을 가져보자 🙂

 

프로토콜지향 프로그래밍(Protocol-Oriented-Programming, POP)란 Swift 언어에서 중요한 패러다임 중 하나로, 객체 지향 프로그래밍과 함께 사용할 수 있지만 보다 유연하고 모듈화된 코드를 작성할 수 있게 해준다.

 

프로토콜 지향 프로그래밍에서는 코드 재사용성을 극대화하고, 다형성을 달성하기 위해 프로토콜을 활용한다고 이해하면 된다!

 

먼저 기본인 프로토콜에 대해서 알아보자

프로토콜은 특정 작업이나 속성에 대한 인터페이스를 정의해두는 꼭 지켜야 하는 규칙이라고 이해하면 된다. 클래스, 구조체, 열거형 등 프로토콜을 채택하면 해당 프로토콜에서 정의한 메서드나 속성을 반드시 구현해야 한다.

 

Swift에서는 다중 상속이 불가능하지만 여러 프로토콜을 채택할 수 있어 유사한 기능을 제공한다!

protocol Drivable {
		var speed: Double { get set }
		func drive()
}

다음은 프로토콜 지향 프로그래밍의 특징을 살펴보자

  • 구현 가능 프로토콜(Protocol with Default Implementation) : Swift 에서는 프로토콜에 기본 구현을 제공할 수 있다. 이로 인해 프로토콜을 채택한 모든 타입에서 공통된 동작을 쉽게 재사용할 수 있다.
protocol Drivable {
    var speed: Double { get set }
    func drive()
}

extension Drivable {
    func drive() {
        print("Driving at \\(speed) km/h")
    }
}
  • 프로토콜 확장(Protocol Extensions) : 프로토콜을 확장해 기본 구현을 추가하거나 새로운 메서드와 속성을 추가할 수 있다. 이렇게 구현하면 프로토콜을 채택한 모든 타입에서 해당 구현을 사용할 수 있다.
extension Drivable {
    func increaseSpeed(by amount: Double) {
        speed += amount
        print("Increased speed to \\(speed) km/h")
    }
}
  • 프로토콜 합성(Protocol Composition) : 여러 프로토콜을 결합하여 새로운 프로토콜을 만들거나, 함수의 매개변수나 반환 타입으로 사용할 수 있다.
protocol Flyable {
		var altitude: Double { get set }
		func fly()
}

typealias FlyableDrivable = Drivable & Flyable

func testFlyableDrivable(vehicle: FlyableDrivable) {
		vehicle.drive()
		vehicle.fly()
}

프로토콜 지향 프로그래밍 VS 객체 지향 프로그래밍

  • 클래스 계층 구조 대신 프로토콜 사용 : 객체 지향 프로그래밍에서는 클래스를 계층적으로 상속받아 기능을 확장하는 방식으로 코드를 구성한다. 반면에 프로토콜 지향 프로그래밍은 클래스를 상속 받는 대신 여러 프로토콜을 채택하여 필요에 따라 기능을 조합하는 방식이다.
  • 구조체와 함께 사용 : POP에서는 클래스를 사용하는 대신, 구조체와 프로토콜을 함께 사용하는 경우가 많다 구조체는 값 타입으로 복사 시 독립적인 복사본이 생성되며, 이는 멀티스레드 환경에서 안전한 코드를 작성하는데 유리하다.

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

  • 유연성 : 프로토콜을 사용하면 서로 다른 타입에 동일한 인터페이스를 강제할 수 있다. 이로 인해 코드의 유연성과 재사용성이 높아진다.
  • 구현 강제 : 프로토콜을 채택한 타입은 해당 프로토콜에서 요구하는 모든 속성과 메서드를 구현해야 하므로 코드의 일관성을 유지할 수 있다.
  • 다형성 : 프로토콜을 사용하면 여러 타입을 동일한 방식으로 처리할 수 있다. 예를 들어 여러 타입이 Drivable 프로토콜을 채택하면 해당 타입의 객체들을 같은 방식으로 조작할 수 있다.
protocol Drivable {
    var speed: Double { get set }
    func drive()
}

extension Drivable {
    func drive() {
        print("Driving at \\(speed) km/h")
    }
}

struct Car: Drivable {
    var speed: Double
    var fuelLevel: Double
    
    func refuel() {
        print("Refueling the car.")
    }
}

struct Bicycle: Drivable {
    var speed: Double
}

let myCar = Car(speed: 120, fuelLevel: 0.8)
let myBicycle = Bicycle(speed: 15)

myCar.drive()         // Driving at 120 km/h
myBicycle.drive()     // Driving at 15 km/h

예제를 살펴보면 Car 와 Bicycle 모두 Drivable 프로토콜을 채택해 drive 메서드를 사용하고 있다. Car와 Bicycle은 서로 다른 타입이지만 동일한 프로토콜을 채택함으로써 동일한 인터페이스를 제공 받는다 🙂

 

프로토콜 지향 프로그래밍은 유연하고 재사용 가능한 코드를 작성하는데 매우 유용하다 OOP의 단점을 보완하고 특히 Swift의 구조체와 같은 값 타입과 함께 사용하면 더 큰 장점을 발휘한다. 이 프로토콜 지향 프로그래밍을 잘 파악해서 모듈화된 유지보수가 쉬운 코드를 작성하도록 노력하자!!

 

오늘은 여기까지!!