Swift 정리 - 23. 프로토콜 지향 프로그래밍


WWDC15 : Protocol-Oriented Programming in Swift

Protocol Default Implementation

프로토콜은 프로토콜을 채택할 타입이 구현해야 하는 요구사항을 나타낸 청사진이고, 익스텐션은 기존 타입의 기능을 확장한다.

만약 프로토콜 하나를 만들고 여러 타입에서 이 프로토콜을 채택해서 타입마다 같은 메서드, 프로퍼티, 서브스크립트를 구현해야 한다면 중복 코드가 어마무시하게 많이 생성될 것이고 이들을 유지보수하는 것도 힘들어진다.

protocol Animal {
    func eat()
    func run()
}

class Human: Animal {
    func eat() {
        print("eat pizza")
    }
    
    func run() {
        print("\(type(of: self)) running")
    }
}

class Cat: Animal {
    func eat() {
        print("eat fish")
    }
    
    func run() {
        print("\(type(of: self)) running")
    }

}

// ... 프로토콜을 채택하는 동물 클래스가 많아질 때마다 eat, run 함수를 매번 구현해줘야 한다.
// 심지어 run() 함수는 모든 클래스에서 동일한 동작을 한다

이때 익스텐션과 프로토콜을 결합해서 사용한다.

protocol Animal {
    func eat()
    func run()
}

// protocol default implementation
extension Animal {
    func run() {
        print("\(type(of: self)) running")
    }
}

class Human: Animal {
    func eat() {
        print("eat pizza")
    }
}

class Cat: Animal {
    func eat() {
        print("eat fish")
    }
}

let human = Human()
human.run() // Human running

let cat = Cat()
cat.run() // Cat running

위 코드에서는 Animal 프로토콜을 확장해서 프로토콜이 요구하는 기능(run())을 실제로 구현해줬다. 따라서 만약 프로토콜을 채택하는 타입에서 run()을 구현하지 않으면 프로토콜 익스텐션에 구현된 기능을 사용한다. 프로토콜과 익스텐션을 결합해서 코드의 재사용성이 증가했다.

이처럼 프로토콜의 요구사항을 익스텐션을 통해 구현하는 것을 protocol default implementation이라 한다.

여기에 제네릭까지 더하면 코드의 재사용성은 훨씬 좋아진다.

protocol Container {
    associatedtype Item
    
    var items: [Item] { get set }
    
    mutating func append(item: Item)
    subscript(i: Int) -> Item { get }
}

// default protocol implementation
extension Container {
    mutating func append(item: Item) {
        self.items.append(item)
    }
    
    subscript(i: Int) -> Item {
        return self.items[i]
    }
}

extension Container where Item: Equatable {
    func compare(i: Int, j: Int) -> Bool {
        if items[i] == items[j] {
            return true
        }
        
        return false
    }
}

struct Stack<Item>: Container {
    var items: [Item]
    
    init() {
        self.items = []
    }
}

var stack = Stack<Int>()
stack.append(item: 1)
stack.append(item: 2)
stack.append(item: 1)

print(stack.compare(i: 0, j: 1))
print(stack.compare(i: 0, j: 2))