1. 객체지향 프로그래밍(OOP)
1-1. 객체지향의 기본 개념
객체(Object)와 메시지(Message)로 구성된다
객체지향에서는 소프트웨어를 하나의 독립적이고 유기적인 객체의 집합으로 보고, 이 객체들이 서로 상호작용하여 문제를 해결해나가는 구조를 만들어 나간다
객체는 외부에서 접근 가능한 인터페이스를 통해 상호작용하고, 외부에서는 객체 내부의 상태나 구현 방식을 알 필요 없이 오직 인터페이스를 통해서만 상호작용한다
1-2. 객체(Object)와 메시지(Message) 란?
- 객체란 데이터와 이를 조작하는 동작(메서드)를 하나의 단위로 묶은 것이다
- 메시지란 서로 독립적인 존재인 객체가 상호작용할 수 있게 하는 도구다(객체 ↔ 메시지 ↔ 객체)
1-3. 객체지향 설계의 5대 원칙(SOLID 원칙)
1) SRP (Single Responsibility Principle, 단일 책임 원칙)
하나의 클래스는 하나의 책임만 가져야 한다
하나의 클래스가 여러 책임(기능)을 가지게 되면서로 영향을 주기 때문에 코드의 변경이 어려워지고 오류가 발생할 가능성이 높아진다(연쇄작용)
또한 책임을 적절히 분배하여 코드의 가독성이 좋아지고 유지보수가 용이해진다
2) OCP (Open/Closed Principle, 개방-폐쇄 원칙)
클래스는 확장에는 개방적이어야 하고 변경에는폐쇄적이어야 한다
기존 코드를 변경하지 않고도 새로운 기능을 추가할 수 있도록 설계를 해야 한다
클래스의 코드를 변경하는 것은 해당 클래스를 사용하는 모든 시스템에 영향을 주는 것으로, 추가 기능을 부여하고 싶다면 기존 기능을 변경하는 것이 아닌 새로운 함수를 추가하는 것이다
3) LSP (Liskov Substitution Principle, 리스코프 치환 원칙)
자식 클래스는 언제나 부모 클래스로 대체될 수 있어야 한다
상속을 받은 하위(자식) 클래스는 상위(부모) 클래스의 기능을 확장할 수 있지만 상위(부모) 클래스와 동일하게 동작해야 한다
즉, 하위 클래스가 상위 클래스의 행동을 깨뜨리지 않고 호환되도록 설계해야 한다
4) ISP (Interface Segregation Principle, 인터페이스 분리 원칙)
클라이언트는 사용하지 않는 인터페이스에 의존하지 않아야 한다
하나의 일반적인 큰 인터페이스보다는 여러 개의 구체적이고 작은 인터페이스를 만드는 것이 바람직하다 = 사용하지 않는 인터페이스는 구현하지 말아야 한다
이를 통해 특정 기능만을 필요로 하는 클라이언트가 불필요한 의존성을 갖지 않도록 한다
5) DIP (Dependency Inversion Principle, 의존성 역전 원칙)
상위 모듈은 하위 모듈에 의존해서는 안 되며 둘 다 추상화에 의존해야 한다
상위 모듈과 하위 모듈이 직접 의존하지 않도록 하여 시스템의 유연성을 높인다
이를 통해 상위 모듈의 변경 없이 하위 모듈을 교체할 수 있게 된다
이게 뭔소리..? 인가 싶어서 찾아본 예시
직접 연결하지 말고 중간에 연결할 수 있는 어댑터를 둬라
만약 집에 여러가지 전자기기(TV, 에어컨, 스피커)가 있다고 가정했을 때, 각자 리모컨이 다르면 불편하다
모든 기기를 제어할 수 있는 범용 신호를 보내는 리모컨이 있다고 생각하자. 리모컨의 신호를 받으면 각각의 기기는 스스로 동작한다
새로운 기기가 생겨도 해당 리모컨에 맞춰 연결만 해주면 된다(유연성, 확장성)
2. 프로토콜
2-1. 프로토콜의 기본 개념
데이터의 형식을 지정하고 처리하기 위한 표준화된 규약
객체지향 프로그래밍에서는 클래스와 구조체의 공통 기능을 정의하는데 사용된다
클래스, 구조체, 열거형에서 특정 프로토콜을 채택하면 그 프로토콜이 요구하는 속성이나 메서드를 모두 구현해야 한다
에 대한 예시!
protocol AnimalSound {
func makeSound() // 소리를 내는 기능을 약속
}
struct Dog: AnimalSound {
func makeSound() {
print("Woof!")
}
}
class Cat: AnimalSound {
func makeSound() {
print("Meow!")
}
}
위 예시를 보면 AnimalSound 프로토콜 내부에 makeSound()라는 함수가 포함되어 있다
그리고 Dog, Cat은 각각 AnimalSound 프로토콜을 채택한다
AnimalSound 프로토콜 내부에서 print를 미리 정의해 버리게 되면 해당 프로토콜을 채택한 Dog, Cat은 동일한 소리를 내게 된다(같은 방식으로 동작하게 된다)
여기서 프로토콜의 특징을 알 수 있는데,
프로토콜 자체는 특정 기능의 약속만 정의하고, 실제 구현 내용은 담지 않는 다는 것이다
2-2. 프로토콜을 사용하는 이유
예제에서 알 수 있듯이 프로토콜을 사용하는 이유는 코드 구조를 명확하게 하고 코드의 유연성과 재사용성을 높이는 것이다
1) 일관성 있는 인터페이스 제공
프로토콜은 특정 기능을 제공하도록 약속한다
공통 기능을 강제하여(ex. makeSound()) 프로토콜을 채택한 타입들이 동일한 인터페이스를 갖게되어 코드의 일관성을 유지한다
2) 다형성 제공(Polymorphism)
프로토콜을 사용하면 서로 다른 타입이지만, 같은 규약을 준수하게 된다 → 하나의 공통 타입처럼 다룰 수 있다
(Cat과 Dog는 모두 Animal 타입이다)
3) 의존성 역전 원칙(DIP) & 코드 재사용성
코드의 의존성을 줄이고 다른 객체나 기능과 느슨하게 결합할 수 있다 → 테스트가 용이하고 코드 재사용성이 높아진다
protocol NetworkService {
func fetchData() -> String
}
class RealNetworkService: NetworkService {
func fetchData() -> String {
return "Real data from network"
}
}
class MockNetworkService: NetworkService {
func fetchData() -> String {
return "Mock data for testing"
}
}
위 예제를 보면 실제 네트워크를 요청하는 함수 RealNetworkService와 관계없이 테스트 목적의 MockNetworkService를 만들어 테스트 할 수 있다
4) 프로토콜 지향 프로그래밍(POP)
Swift는 객체지향 뿐만 아니라 프로토콜 지향 프로그래밍을 특히 더 잘 활용할 수 있다고 한다
기본 동작을 하는 코드를 작성하고, 각 타입에서 필요한 부분을 재정의하거나 확장(PE)함으로서 유연성을 가진다
[ 객체지향 프로그래밍(OOP)과 프로토콜지향 프로그래밍(POP)의 차이점 ]
특징 | 객체지향 프로그래밍(OOP) | 프로토콜지향 프로그래밍(POP) |
기본개념 | 클래스 상속을 통한 코드 재사용 | 프로토콜과 구성(composition)을 통한 기능 조합 |
상속구조 | 단일상속 | 다중 프로토콜 채택 가능 |
다형성 | 클래스 상속을 통한 다형성 | 프로토콜 기반 다형성 |
유연성 | 상속 계층에 제한 | 필요한 기능만 조합 가능 |
기본 구현방식 | 클래스 기본 구현 | 프로토콜 확장을 통한 기본 구현 |
📎 참고자료
'💡 Today I Learned > 스터디 자료정리' 카테고리의 다른 글
[스터디9일차] CollectionView (0) | 2024.11.26 |
---|---|
[스터디8일차] Diffable Data Source (0) | 2024.11.13 |
[스터디6일차] 테스트 코드👩🏻🔧 (1) | 2024.10.30 |
[스터디5일차] MVVM 패턴 (0) | 2024.10.25 |
[스터디4일차][RxSwift] RxCocoa (1) | 2024.10.22 |