안녕하세요!
지난 블로그에는 객체의 속성을 정의하는 프로퍼티와 동작을 정의하는 메서드에 대해 알아보며 클래스와 구조체를 더욱 풍부하게 만드는 방법을 익혔죠. 오늘은 객체 지향 프로그래밍의 가장 강력한 기능 중 하나이자 코드 재사용성의 꽃이라고 할 수 있는 상속(Inheritance)에 대해 깊이 파고들 것입니다.
상속은 마치 부모가 자녀에게 유전자를 물려주듯이, 기존 클래스의 특성과 기능을 새로운 클래스가 물려받아 자신의 것처럼 사용할 수 있게 해주는 메커니즘입니다. 이를 통해 우리는 이미 잘 만들어진 코드를 재활용하고, 거기에 새로운 기능을 추가하거나 기존 기능을 변경하여 더욱 효율적이고 유연한 프로그램을 만들 수 있습니다.
1. 상속(Inheritance)이란 무엇인가요?
상속은 한 클래스가 다른 클래스의 프로퍼티, 메서드, 서브스크립트 등을 물려받는 과정입니다.
- 수퍼클래스 (Superclass) / 부모 클래스 / 상위 클래스: 기능을 물려주는 기존 클래스입니다.
- 서브클래스 (Subclass) / 자식 클래스 / 하위 클래스: 수퍼클래스로부터 기능을 물려받는 새로운 클래스입니다.
Swift에서 클래스(Class)만 상속을 지원합니다. 구조체(struct)와 열거형(enum)은 상속을 지원하지 않습니다.
💡 상속을 사용하는 이유:
- 코드 재사용성:
- 중복되는 코드를 줄이고, 이미 구현된 기능을 재활용하여 개발 시간을 단축합니다.
- 하나의 수퍼 클래스에 중요한 함수를 구현하고, 동일한 서브 클래스들에서는 수퍼클래스의 코드를 사용하게 되면, 하나의 구현으로 여러 개의 하위 서브클래스에서 동일하게 사용하게 되고, 수정할 때는 수퍼클래스만 수정하면, 서브클래스에서는 모두 다 적용이 됩니다.
- 확장성:
- 기존 클래스를 수정하지 않고도 새로운 기능을 추가하거나 특정 기능을 변경할 수 있습니다.
- 비슷하지만, 독특한 특성을 가지고 있는 클래스가 있다면, 비슷한 수퍼클래스의 서브 클래스로 만들어서 기존 클래스의 기능을 그대로 사용하면서, 독특한 특성만 추가적으로 구현해서 사용할 수 있습니다.
- 계층 구조:
- 관련된 클래스들을 계층적인 관계로 조직하여 프로그램의 구조를 명확하게 만듭니다. (예: Vehicle -> Car -> ElectricCar)
- 사람들이 생각하기 쉽게 계층적인 관계로 만들면, 이해도 쉽고, 프로그램의 구조도 이해하기 쉽게 됩니다.
- 다형성(Polymorphism) 구현의 기반:
- 다양한 타입의 객체를 동일한 인터페이스로 다룰 수 있게 합니다.
- 다양한 서브클래스를 만들어도, 수퍼클래스의 함수가 동일하므로, 동일한 형태로 호출할 수 있어서 확장성과 유지보수가 쉬워집니다.
1.1. 상속의 기본 문법
상속은 서브클래스 이름 뒤에 콜론(:)을 붙이고 수퍼클래스 이름을 작성하여 표현합니다.
//Swift 예제
class SuperclassName {
// 수퍼클래스의 프로퍼티와 메서드
}
class SubclassName: SuperclassName {
// 서브클래스만의 새로운 프로퍼티와 메서드 추가
// 또는 수퍼클래스의 메서드/프로퍼티를 재정의 (Override)
}
2. 상속 기본 예시: 동물과 강아지
그럼, 간단한 예시를 통해 상속의 동작 방식을 이해해 봅시다. 모든 동물은 이름이 있고 소리를 낼 수 있습니다. 강아지는 동물의 일종이며, 추가적으로 짖을 수 있습니다.
//Swift 예제
class Animal { // 수퍼클래스
var name: String
init(name: String) {
self.name = name
}
func makeSound() {
print("\(name)이(가) 소리를 냅니다.")
}
}
class Dog: Animal { // Dog 클래스는 Animal 클래스를 상속받음
var breed: String
init(name: String, breed: String) {
self.breed = breed
super.init(name: name) // 수퍼클래스의 초기화 메서드 호출
}
func bark() {
print("\(name)이(가) 멍멍 짖습니다!")
}
}
let genericAnimal = Animal(name: "동물")
genericAnimal.makeSound() // 출력: 동물이(가) 소리를 냅니다.
let myDog = Dog(name: "바둑이", breed: "진돗개")
myDog.makeSound() // 출력: 바둑이(가) 소리를 냅니다. (Animal의 메서드 사용)
myDog.bark() // 출력: 바둑이(가) 멍멍 짖습니다! (Dog의 고유 메서드 사용)
print("우리 강아지의 품종은 \(myDog.breed)입니다.") // 출력: 우리 강아지의 품종은 진돗개입니다.
Dog
클래스는 Animal 클래스의 name 프로퍼티와 makeSound() 메서드를 별도의 구현 없이 바로 사용할 수 있습니다. 또한, Dog 클래스만의 새로운 breed 프로퍼티와 bark() 메서드를 추가할 수 있습니다. init 메서드 내에서 super.init()을 호출하여 부모 클래스의 초기화 작업을 수행해야 합니다.
3. 메서드 오버라이딩 (Method Overriding): 기능 재정의하기
서브클래스는 수퍼클래스로부터 물려받은 인스턴스 메서드, 타입 메서드, 프로퍼티, 서브스크립트를 자신의 구현으로 재정의(Override)할 수 있습니다. 재정의하려는 메서드나 프로퍼티 앞에 override 키워드를 반드시 붙여야 합니다. 이는 개발자의 의도를 명확히 하고, 실수로 인한 오버라이딩을 방지합니다.
//Swift 예제
class Cat: Animal { // Cat 클래스도 Animal 클래스를 상속받음
override func makeSound() { // makeSound 메서드 오버라이딩
print("\(name)이(가) 야옹하고 울어요.")
}
func scratch() {
print("\(name)이(가) 발톱으로 긁습니다.")
}
}
let myCat = Cat(name: "나비")
myCat.makeSound() // 출력: 나비(이)가 야옹하고 울어요. (Cat의 오버라이드된 메서드 사용)
myCat.scratch() // 출력: 나비(이)가 발톱으로 긁습니다.
Cat 클래스는 Animal의 makeSound() 메서드를 자신의 방식대로 재정의했습니다.
3.1. 프로퍼티 오버라이딩
수퍼클래스의 저장 프로퍼티나 연산 프로퍼티를 오버라이드하여 서브클래스에서 다른 연산 프로퍼티로 재정의할 수 있습니다.
//Swift 예제
class Vehicle {
var currentSpeed = 0.0
var description: String {
return "시속 \(currentSpeed) km로 이동 중입니다."
}
}
class Car: Vehicle {
var gear = 1
// description 연산 프로퍼티 오버라이드
override var description: String {
return super.description + " 기어 \(gear)단으로 주행 중입니다."
}
}
let bicycle = Vehicle()
bicycle.currentSpeed = 5.0
print(bicycle.description) // 출력: 시속 5.0 km로 이동 중입니다.
let someCar = Car()
someCar.currentSpeed = 80.0
someCar.gear = 3
print(someCar.description) // 출력: 시속 80.0 km로 이동 중입니다. 기어 3단으로 주행 중입니다.
super.description을 사용하여 수퍼클래스의 구현을 호출한 후 추가적인 내용을 덧붙였습니다.
4. final 키워드: 상속 및 오버라이딩 방지
클래스, 메서드, 프로퍼티, 또는 서브스크립트 선언 앞에 final 키워드를 붙이면, 다른 클래스가 이를 상속받거나 오버라이드하는 것을 방지할 수 있습니다. 이는 특정 기능을 더 이상 변경할 수 없도록 확정할 때 사용합니다.
//Swift 예제
final class FinalClass { // 더 이상 상속될 수 없는 클래스
var value = 0
final func doSomething() { // 더 이상 오버라이드될 수 없는 메서드
print("FinalClass의 작업")
}
}
// class MySubClass: FinalClass { } // 컴파일 에러! FinalClass는 final 이므로 상속 불가
class BaseCalculation {
func calculate() {
print("기본 계산")
}
}
class AdvancedCalculation: BaseCalculation {
final override func calculate() { // 오버라이드는 가능하나, 이 서브클래스 아래에서는 더 이상 오버라이드 불가
print("고급 계산")
}
}
class UltimateCalculation: AdvancedCalculation {
// override func calculate() { } // 컴파일 에러! calculate는 final 이므로 오버라이드 불가
}
정리하며
오늘은 Swift 객체 지향 프로그래밍의 핵심 개념인 상속(Inheritance)에 대해 알아보았습니다.
- 상속: 수퍼클래스의 특성과 기능을 서브클래스가 물려받아 코드 재사용성과 확장성을 높입니다.
- 메서드 오버라이딩: 서브클래스에서 수퍼클래스의 기능을 override 키워드를 사용하여 재정의할 수 있습니다.
- super 키워드: 오버라이드된 메서드 내에서 수퍼클래스의 구현을 호출할 때 사용합니다.
- final 키워드: 클래스, 메서드, 프로퍼티 등이 더 이상 상속되거나 오버라이드되는 것을 방지합니다.
상속은 강력한 도구이지만, 과도하게 사용하면 코드의 복잡성을 증가시킬 수 있으므로 신중하게 사용해야 합니다. Swift에서는 상속 외에도 프로토콜(Protocol)을 통한 다형성 구현을 더 선호하는 경향이 있다는 점도 기억해두시면 좋습니다.
다음 시간에는 Swift 인스턴스의 생성과 해제를 다루는 초기화(Initialization)에 대해 알아보겠습니다.
궁금한 점이 있다면 언제든지 댓글로 남겨주세요!
0 comments:
댓글 쓰기