클래스의 상속
기본 개념
- 수직 확장
- 본질적으로 성격이 비슷한 타입을 새로 만들어
1). 데이터(저장속성)를 추가(즉, 메모리가 추가)하거나
- 저장 속성은 메모리 공간을 갖는다
2). 기능(메서드)를 변형시켜서 사용
- 본질적으로 새로운 타입을 만드는 것이지만
- 기본적으로 데이터(저장 속성)을 추가하는 관점에서 생각한다
- 저장 속성 : 기본적으로 메모리 공간을 가지고 있음.
class Person {
var id = 0
var name = "이름"
var email = "abc@gmail.com"
}
class Student: Person {
var studentId = 0
}
class Undergraduate: Student {
var major = "전공"
}
var person1 = Undergraduate()
person1.id
person1.studentId
- 위에 아무것도 상속하지 않은 클래스 (처음 클래스) : base class
- 상대적으로 child class/parent class, sub class/super class
final
- class 선언 시 final을 붙이면 상속할 수 없다
- 멤버 앞에 final을 붙이면, 재정의가 불가능하다
(재정의 : 상위 클래스에 존재하는 멤버를 변형하는 것)
재정의 (Overriding)
- "재정의가 불가" <- 기존 메모리 공간을 그대로 유지해야 하기 때문
- overloading : 함수에서 하나의 이름에 여러 함수를 대응시켜서 사용
- overriding : 재정의. 클래스의 상속에서 상위클래스의 속성/메서드의 기능을 약간 변형하여 사용하는 것
- 재정의 : sub class에서 super class의 동일한 멤버를 변형하여 구현한다
- 재정의 : 상위 클래스에서 존재하는 멤버를 변형하는 것
- 재정의가 가능한 대상 (각각 재정의 방식이 다르다)
- 속성 (저장 속성은 불가능 -> 데이터 구조의 변형 불가)
- 데이터(저장 속성)는 추가만 가능
- 계산 속성, ... 가능
- 계산 속성은 실질적으로 메서드이기 때문에 메서드쪽에 더 가까움
- 메서드 (메서드, 서브스크립트, 생성자 -> 기능의 변형 가능)
- 기능(메서드)는 추가 및 변형(대체) 가능
- 상위 구현을 호출하는 경우도 많다 (super.~)
- 메모리 구조가 다르기 때문. (공간 차지 vs 주소 저장) (강의자료 참고)
- 상속 과정에서 저장 속성은 이전 super class의 데이터를 가리킴
- 하지만 메서드들은 계속 새로운 배열을 생성
class SomeSuperclass {
var aValue = 0
func doSomething() {
print("Do something")
}
}
class SomeSubclass: SomeSuperclass {
** override var aValue = 3
override var aValue: Int {
get {
return 1
}
set {
super.aValue = newValue
}
}
override func doSomething() {
super.doSomething()
print("Do something 2")
}
}
재정의 방식
속성의 재정의 (엄격)
- 실질적으로 타입 속성을 재정의하는 일은 매우 드물다. 일단 배제하고 생각하자
- 저장 속성의 재정의
- 원칙적으로 불가능 (고유의 메모리 공간 유지 필요)
- 하위 클래스에서 메모리 공간을 바꾸는 방식으로의 재정의는 불가능
- 메서드 형태로 추가하는 방식의 재정의 가능
- 읽기/쓰기 가능한 계산 속성으로 재정의 가능.
(기능 축소는 불가능. read-only로 불가능)
- 기존 저장 속성을 생각해보면, 읽기/쓰기가 모두 가능하다
- 따라서 이걸 읽기만 가능한 계산 속성으로 기능을 축소하는 건 불가능하다
- 속성 감시자 추가 가능 (메서드)
class Vehicle {
var currentSpeed = 0.0
var halfSpeed: Double {
get {
return currentSpeed / 2
}
set {
currentSpeed = newValue * 2
}
}
}
class Bicycle: Vehicle {
var hasBasket = false
저장 속성을 재정의 -> 계산 속성으로 재정의했다
override var currentSpeed: Double {
get {
return super.currentSpeed
}
set {
super.currentSpeed = newValue
}
}
저장 속성을 재정의 -> 속성 감시자를 추가하는 재정의
override var currentSpeed: Double {
willSet {
print("값이 \(currentSpeed)에서 \(newValue)로 변경 예정")
}
didSet {
print("값이 \(oldValue)에서 \(currentSpeed)로 변경 예정")
}
}
계산 속성을 재정의
override var halfSpeed: Double {
get {
return super.currentSpeed / 2
}
set {
super.currentSpeed = newValue * 2
}
}
계산 속성을 재정의 + 속성 감시자 추가
override var halfSpeed: Double {
willSet {
print("값이 \(halfSpeed)에서 \(newValue)로 변경 예정")
}
didSet {
print("값이 \(oldValue)에서 \(halfSpeed)로 변경 예정")
}
}
메서드(계산 속성)의 재정의
- 기능의 범위 축소하는 재정의는 불가능
- 읽기 전용 -> 읽기/쓰기 가능한 속성으로 재정의 가능 (확장o)
- 속성 감시자 추가하는 재정의는 불가능
- 읽기 전용 속성은 어차피 값이 변할 일이 없다
- 따라서 속성 감시자를 추가한다는 게 논리적으로 맞지 않음
- 읽기/쓰기 -> 읽기만 가능한 속성으로 제정의 불가능 (기능 제한x)
- 속성 감시자 추가하는 재정의 가능 (관찰 가능)
class Vehicle1 {
var currentSpeed = 0.0
var datas = ["1", "2", "3", "4", "5"]
func makeNoise() {
print("경적을 울린다.")
}
subscript(index: Int) -> String {
get {
if index > 4 {
return "0"
}
return datas[index]
}
set {
datas[index] = newValue
}
}
}
class Bicycle1: Vehicle1 {
상위 -> 하위 호출 가능
override func makeNoise() {
super.makeNoise()
print("자전거가 지나간다고 소리친다")
}
하위 -> 상위 호출 가능
override func makeNoise() {
print("자전거가 지나간다고 소리친다")
super.makeNoise()
}
상위 아예 무시 가능
override func makeNoise() {
print("경적을 울리고, 자전거가 지나간다고 소리친다.")
}
overide subscript(index: Int) -> String {
get {
if index > 4 {
return "888"
}
return super[index]
}
set {
supet[index] = newValue
}
}
}
let v = Bicycle1()
v.currentSpeed
v.makeNoise()
인스턴스 속성의 대원칙
- 저장 속성 재정의는 원칙적으로 불가능,
but 메서드 방식으로 추가는 가능
- 계산 속성의 유지/확장은 가능, 축소는 불가능
- 속성 감시자(메서드)를 추가하는 재정의는 언제나 가능,
but 읽기전용 계산 속성을 관찰하는 건 의미 없으므로 불가능
초기화 (Initialization)
- class, struct, enum에서 인스턴스를 생성하는 과정. 메모리에 찍어내는 과정
- 각 저장 속성에 대한 초기값 설정
-> 인스턴스를 사용가능한 상태로 만든다
- 생성자의 실행이 완료되면, 인스턴스의 모든 저장속성이 초기값 가지도록 하는 것
-> 생성자의 역할
- 초기화의 방법
- 저장 속성의 선언과 동시에 값 저장
- 저장 속성을 옵셔널로 선언 (초기값 없어도 nil로 초기화)
- 생성자에서 값 초기화
- 1, 2번 방법이면 생성자 구현하지 않아도 초기화 가능
생성자 구현
class Color {
let red: Double
let green: Double
let blue: Double
init() {
red = 0.0
green = 0.0
blue = 0.0
}
init(white: Double) {
red = white
green = white
blue = white
}
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
}
var color = Color()
var color2 = Color.init()
color = Color(white: 7.0)
color = Color.init(white: 7.0)
color = Color(red: 7.0, green: 7.0, blue: 7.0)
color = Color.init(red: 7.0, green: 7.0, blue: 7.0)
Memberwise Initializer
struct Color1 {
var red: Double
var green: Double
var blue: Double
}
color1 = Color1(red: 1.0, green: 1.0, blue: 1.0)
- 생성자를 구현하지 않으면, 컴파일러가 기본 생성자를 자동으로 생성 -> "init()"
- 생성자를 구현하면, 기본 생성자를 자동으로 생성하지 않는다
- 구조체는 저장 속성들이 기본값을 가지고 있어도(다 가지고 있지 않아도),
추가적으로 Memberwise Initializer 자동으로 제공
- 직접적으로 생성자 구현하면,
Memberwise Initializer 제공하지 않는다