먼저 두 개념의 본질적인 차이를 이해하는 것이 중요해요.
중심
Class를 상속해요.
철학
너는 무엇인가? (What is it?)
방식
공통된 기능을 부모 클래스에 담고, 자식 클래스가 이를 물려받아요.
장점
현실 세계의 구조를 직관적으로 설계할 수 있고, 상속을 통해 공통 기능을 체계적으로 관리할 수 있어요.
단점
수직적인 구조라 깊어질수록 복잡해지고, 필요없는 기능까지 상속받는 ‘무거운’ 객체가 생길 수 있어요.
중심
프로토콜(Protocol)과 확장(Extension)
철학
너는 무엇을 할 수 있는가? (What can it do?)
방식
기능을 프로토콜이라는 ‘설계도’로 정의하고, 필요한 객체가 이를 채택해요.
장점
수평적인 확장이 가능하며, 클래스뿐만 아니라 Struct와 Enum에도 적용할 수 있어 매우 유연해요.
단점
프로토콜이 너무 파편화되면 전체 구조 파악이 어렵고 설계가 복잡해져요.
| 구분 | OOP (객체 지향) | POP (프로토콜 지향) |
|---|---|---|
| 핵심 키워드 | 상속 (Inheritance) | 구성 (Composition) |
| 참조 타입 | 클래스(Class) 위주 | 구조체, 클래스, 열거형 모두 가능 |
| 관계 형성 | 수직적 (부모-자식) | 수평적 (기능 단위 결합) |
| 유연성 | 상대적으로 낮음 (다중상속 불가) | 매우 높음 (여러 프로토콜 채택 가능) |
애플은 2015년 WWDC에서 Swift를 “세계 최초의 프로토콜 지향 언어” 라고 선포했어요.
Swift에서 POP가 빛나는 이유는 바로 Protocol Extension 덕분이에요.
기본구현
원래 프로토콜은 “이런 기능을 만들어라” 라고 명령만 할 뿐 코드를 직접 짜지는 못했어요. 하지만 extension 을 사용하면 프로토콜에 실제 기능을 미리 구현해둘 수 있어요.
이렇게 하면 상속의 장점(코드 재사용)은 챙기면서, 상속의 단점(강한 결합도)은 피할 수 있게 돼요.
상속의 한계를 극복하는 “수평적 확장”
구조체는 원래 상속이 불가능해요. 하지만 실무에서는 여러 구조체가 공통된 기능을 가져야 할 때가 많죠.
장점
프로토콜을 사용하면 구조체도 여러 개의 기능을 “채택” 하여 마치 다중 상속을 받은 것 같은 효과를 낼 수 있습니다.
성능
클래스처럼 무거운 상속 계층을 만들지 않고도, 필요한 기능만 쏙쏙 뽑아 구현하므로 메모리 오버헤드가 적고 값 타입 특유의 빠른 속도를 유지할 수 있어요.
기본 구현을 통한 코드 재사용
구조체는 코드를 복사해서 붙여넣어야 할까요? 아니에요!
강한 결합 해소
클래스 상속은 부모 클래스의 모든 것을 강제로 물려받아야 해요. 클래스는 부모가 변하면 자식도 원치 않게 영향을 받아요.
장점
프로토콜을 타입으로 사용하면 클래스 간의 관계가 “너는 내 자식이다.”가 아니라, “너는 이 기능을 할 줄 아는 애구나” 라는 느슨한 관계로 변하게 돼요. 이는 코드의 수정을 용이하게 하고, 테스트 시 Mock을 갈아 끼우기 매우 좋게 만들어요.
다이아몬드 상속 문제 해결
클래스는 단일 상속만 가능하지만, 프로토콜은 개수 제한 없이 채택할 수 있어요. 복잡한 클래스 계층 없이도 다채로운 객체를 구성할 수 있어요.
| 구분 | Value Type (Struct) | Reference Type (Class) |
|---|---|---|
| 주요 역할 | 상속이 없는 구조체에 다형성 부여 | 강한 상속 관계를 느슨한 관계로 해제 |
| 코드 재사용 | Extension을 통한 기본 동작 공유 | 인터페이스 규격화를 통한 의존성 분리 |
| 안전성 | 값 복사를 통한 Thread-Safe 확보 | 참조 관리의 복잡성 감소 및 유지보수성 향상 |
한줄 요약
값 타입(Struct)에게는 상속의 한계를 넘는 유연함을 주고, 참조 타입(Class)에게는 무거운 상속의 굴레를 벗겨 자유로운 결합을 가능하게 해요.
struct는 주로 스택에 할당되고, 클래스가 힙에 할당됩니다.
스택 할당이 힙 할당보다 빠르죠!