객체지향 프로그래밍의 특징,
실제로 구현하거나 경험한 적 있는 것을 예시와 함께 설명
캡슐화 / 상속 / 다형성 / 추상화
속성, 메서드를 하나의 객체 안에 묶어서 직접 접근을 제한하고
인터페이스를 통해서 조작할 수 있게 함
기존 클래스로 새로운 클래스를 만들고 부모 클래스의 속성, 기능을 물려받아서 재사용이나 확장이 가능하게 하는 것
같은 인터페이스를 통해서 서로 다른 타입의 객체를 다룰 수 있는 것
메서드 오버라이딩 등을 통해 구현
복잡한 내부 구현을 숨기고 필요한 기능만 외부에 노출함
Car 클래스가 기본 클래스 역할을 하고
ElectricCar, HybridCar 등이 Car를 상속받아 기능을 확장하거나 변경
class ElectricCar: Car {
// 전기차 기능 구현
}
class HybridCar: Car {
// 하이브리드 기능 구현
}
Car 타입의 변수에 ElectricCar나 HybridCar 인스턴스를 할당해서 사용
drive()와 stop() 같은 메서드가 open으로 선언돼서 하위 클래스에서 오버라이딩 가능
같은 메시지(drive())를 호출해도 실행되는 메서드는 실제 인스턴스 타입에 따라 다르게 동작함
let car: Car = ElectricCar(brand: "테슬라", model: "Model 3", year: "2025")
car.drive() // ElectricCar에서 오버라이딩된 drive() 실행 가능
Car 클래스가 자동차라는 개념을 추상화해서
브랜드, 모델, 연도, 엔진이라는 공통 속성과 기능(drive(), stop())을 정의
engine은 Engine 프로토콜 타입으로 선언돼서
구체적인 엔진 구현을 추상화하여 다룰 수 있음
open var engine
: Engine 으로 어떤 Engine 프로토콜을 채택한 타입이든 할당 가능하니까
엔진 교체 메서드도 가능하게 설계
public init(brand: String, model: String, year: String, engine: any Engine) {
...
self.engine = engine // Engine 프로토콜 타입으로 추상화
}
protocol에 대해서 설명,
protocol을 활용해서 의존성을 줄인 경험,
protocol을 class 대신 사용하는 것이 왜 도움이 될 수 있는지 설명
Swift에서 규칙이나 청사진 역할을 하는 것? 타입??
특정 메서드, 프로퍼티, 이니셜라이저로 구현하도록 강제하는 인터페이스
클래스, 구조체, 열거형이 프로토콜을 채택하면 규칙에 맞게 기능을 구현해야 함
protocol Drivable {
func drive()
func stop()
}
어떤 클래스가 다른 클래스를 직접 생성해서 의존하면 결합도가 높아져서
유지보수나 테스트가 어려워짐
class Engine {
func start() { print("start") }
}
class Car {
let engine = Engine() // Car가 Engine에 강하게 의존함
func drive() {
engine.start()
print("drive")
}
}
이 경우 Car는 항상 Engine이라는 구체 클래스에 의존해서 바꿔 쓰기 어려운데
protocol EngineProtocol {
func start()
}
class Engine: EngineProtocol {
func start() { print("Engine starts") }
}
class ElectricEngine: EngineProtocol {
func start() { print("Electric engine starts quietly") }
}
class Car {
let engine: EngineProtocol // 프로토콜 타입에 의존
init(engine: EngineProtocol) {
self.engine = engine
}
func drive() {
engine.start()
print("Car drives")
}
}
let combustionEngine = Engine()
let electricEngine = ElectricEngine()
let car1 = Car(engine: combustionEngine)
let car2 = Car(engine: electricEngine)
Car는 EngineProtocol에만 의존하니까 어떤 구체 엔진인지 몰라도 되고
쉽게 변경할 수 있음
이렇게 하면 테스트 할 때 Mock 엔진 주입도 가능해지고 확장성도 좋아짐
직접 의존하면 구현에 너무 묶임 → 유지보수 및 확장 어려움
프로토콜은 구현과 분리된 추상화된 계약이기 때문에 의존성을 느슨하게 만들어 줌
프로토콜은 여러 프로토콜 채택이 가능하지만 클래스는 단일 상속만 가능
클래스가 아닌 타입도 프로토콜을 채택할 수 있어서 더 유연
테스트용 Mock 객체를 만들어 주입하기 편함