존 호프만의 Protocol-Oriented Programming with Swift(스위프트 프로토콜지향 프로그래밍) 1장에 대한 글입니다. 책의 내용을 기본으로 하고 있지만, 개인적으로 많은 고민을 하면서 쓴 글이기 때문에 그런 부분도 반영되어 있습니다. 참고해주세요.
Polymorphosm, OOP, POP
객체지향 프로그래밍(OOP)과 프로토콜지향 프로그래밍(POP)을 살펴보기 전에 먼저 짚고 넘어가야 할 핵심개념이 있다.
그것은 바로 Polymorphism(다형성)
Polymorphism이란, 간단히 말하면, 같은 인터페이스로 여러 타입과 상호작용할 수 있는 개념이다.
(ex) 콜라(캔)와 오렌지주스(플라스틱병) 객체가 각각 있다고 할 때 |
위 예제에서 알 수 있듯이 Polymorphism은 코드의 재사용성을 올려주며, 보다 생산적이고 클린한 구조의 설계를 돕는 철학이다. 이런 Polymorphism은 OOP의 특징 중 하나로 불리기도 하고, OOP에서는 Subclassing, Overloading, Overriding 등을 통해 Polymorphism을 구현한다.
이전의 절차지향 프로그래밍(Procedural Programming)에 비하면 OOP는 뚜렷한 장점들을 가진다. 그러나 OOP 역시 만능은 아니다. OOP에서 Polymorphism을 위해 사용하는 Subclassing 방식에는 불편한 점들도 존재한다. 이런 불편함을 해결할 수 있다며 애플에서 권장하는 것이 바로 POP, 프로토콜지향 프로그래밍 이다. 애플은 Swift가 세계 최초의 프로토콜지향 프로그래밍 언어라고 소개하기도 했다.
이런 배경을 알고나면 마치 POP가 OOP보다 월등하며 OOP에서 POP로 패러다임을 전환해야 한다고 생각할지도 모른다. 그러나 OOP와 POP는 대립적인 관계에 있는 개념이 아니며, 어느 것이 더 우월하다고 말할 수는 없다. 이런 케이스에는 어떤 방식을 적용하는 것이 좋을까 라고 생각하는 것이 좋다. 그러기 위해서는 OOP와 POP가 각각 어떤 방식으로 Polymorphism의 철학을 구현하고 있는지 알아야 하고, 장점과 한계에 대해 알아야 한다. 이런 이해를 바탕으로 설계 과정에서 Subclassing 을 사용할지 Protocol 을 사용할지 판단하는 것이 개발자의 역량이라고 할 수 있다.
이 책의 1장에서는 OOP와 POP가 각각 Polymorphism을 어떤 방식으로 구현하는지 Swift 코드를 통해 예제로 설명한다. 그리고 예제를 확장해가며 OOP에서의 불편한 점들이 무엇인지, POP를 통해 그것을 어떻게 해결할 수 있는지, OOP와 POP의 차이는 무엇인지, 그래서 우리는 OOP를 버리고 POP를 쓰면 되는건지(아님) 등등을 쉽게 설명해준다. 이 모든 결론은 바로 윗 문단이지만, 지금부터 간단히 책의 전개방식을 따라가보자.
Swift 에서 OOP와 POP로 Polymorphism을 구현하는 방식
스위프트에서 슈퍼클래스는 요구 사항에 대한 구현을 제공한다. 스위프트에서의 프로토콜이란 프로토콜을 따르는 타입은 반드시 프로토콜에 명시된 요구 사항을 준수해야 한다는 단순한 계약이다.
(책 본문 중)
가장 단순하고 명료하게 상속과 프로토콜을 비교한 문장이 아닌가 싶다.
OOP에서는 Subclassing, Overloading, Overriding을 통해 Polymorphism을 구현한다. 공통 코드(function 등)는 슈퍼클래스에서 제공해준다. 서브클래스는 그것을 그대로 활용하거나, 필요 시 자신이 따로 구현한 코드로 덮어쓴다.
POP에서는 프로토콜을 통해 Polymorphism을 구현한다. 프로토콜 확장 등을 통해 공통 코드(function 등)를 제공해줄 수 있다.
OOP의 Subclassing이 가지는 불편한 점들
1) 슈퍼클래스에 너무 종속적이다.
서브클래스를 생성하기 위해서는 슈퍼클래스의 코드를 제대로 파악하고 있어야 하며, 생성자와 퍼블릭 함수가 어떻게 돌아가는지 아는 것이 실수를 방지할 확률을 높인다.
슈퍼클래스를 그대로 상속받는 구조이기 때문에, 서브클래스는 자신에게 필요없는 변수나 함수를 무조건 물려받을 수밖에 없다. 괴로운 것은 슈퍼클래스 입장에서도 마찬가지이다. 몇몇 서브클래스에만 필요한 코드들이 슈퍼클래스에 무한추가되어, 수명이 늘어날수록 몸집은 크고 코드는 더러운 클래스가 될 공산이 크다(욕을 많이 먹어서 수명은 점점 더 늘어난다...).
2) Value Type을 사용할 수 없다
Swift에서 Class는 Reference Type(참조 타입)이다. 상속 구조를 사용하기 위해서는 Value Type(값 타입)으로 정의해도 무방한 모델들을 굳이 참조 타입으로 정의해야 하는 불편이 있다.
Swift에서는 참조 타입이 반드시 필요한 상황이 아니라면, 가급적 값 타입을 사용하라고 공식적으로 가이드하고 있다. 참조 타입과 값 타입의 차이, 값 타입을 권장하는 이유에 대해서는 2장에서 자세히 다룬다.
POP의 편리한 점들
1) 위에서 언급한 OOP의 불편한 점들을 해결해준다
구현체 입장에서 알아야 할 것은 '프로토콜이 요구하는 변수와 함수' 뿐이다. 슈퍼클래스와 서브클래스의 의존적인 관계와 다르게, 프로토콜 기반의 구조에서는 프로토콜에 정의된 인터페이스가 무엇인지 슥 보고 그것만 구현해놓으면 된다. 슈퍼클래스를 상속받는 서브클래스들과 달리, 같은 프로토콜을 따르는(conforms to) 사이에도 끈끈하게 엮여있는 부분이 없으므로 각각이 독립적이며 그래서 안전하다. '이 프로토콜을 따른다'는 공통점 밖에 없다.
프로토콜은 참조 타입과 값 타입을 모두 지원한다. Class, Struct, Enum, ... 입맛에 맞게 사용할 수 있다.
2) 다수의 프로토콜을 따르는 것이 가능하다
상속 구조에서는 오직 하나의 슈퍼클래스만 가질 수 있다. 그러나 프로토콜의 경우에는 다수의 프로토콜을 따르는 것이 가능하다. 또한 물려받아 재사용하는 개념이 아니기 때문에, 다중 상속의 문제점으로 언급되는 죽음의 다이아몬드 이슈에서도 자유롭다.
결론
OOP와 POP는 대립적인 관계에 있는 개념이 아니며, 어느 것이 더 우월하다고 말할 수는 없다. 이런 케이스에는 어떤 방식을 적용하는 것이 좋을까 라고 생각하는 것이 좋다. 그러기 위해서는 OOP와 POP가 각각 어떤 방식으로 Polymorphism의 철학을 구현하고 있는지 알아야 하고, 장점과 한계에 대해 알아야 한다. 이런 이해를 바탕으로 설계 과정에서 Subclassing 을 사용할지 Protocol 을 사용할지 판단하는 것이 개발자의 역량이라고 할 수 있다.
앞서 정리했던 결론을 다시 한 번 가져왔다. 프로토콜지향 프로그래밍은 확실히 장점도 많고 깔끔하고 안전하고 쿨해보이고 하지만 이걸 해야한다는 강박에 갇히게 되면 아무 곳에나 프로토콜을 가져다 쓰게 된다(경험담). 특히 팀으로 프로젝트를 진행할 때에는 주의해야 한다(경험담). 죄다 프로토콜로 만들어 놓고 어 왜 이렇게 가독성이 떨어지고 안 멋져보이지 고민했는데, 알고보니 내가 한 것은 POP(Protocol-oriented Programming)가 아니라 POOP💩(Protocol-orientation-obsessed Programming)이었다. 고민하던 때에 찾은 아티클이 하나 있는데, 읽어보면 재미있다 > how to avoid POOP(클릭)
'개발서적 읽으며 끄적끄적 > POP with Swift' 카테고리의 다른 글
프로토콜 확장으로 기능을 추가하기 (0) | 2020.01.01 |
---|---|
프로토콜 컴포지션이 클래스 상속 구조보다 유용한 경우를 설명하기 좋은 예제 (0) | 2019.12.30 |
스위프트에서의 '타입' (0) | 2019.12.26 |