재정의 Override
저번 상속 게시글에서 부모/자식/기반 클래스에 대해 알아봤는데, 자식 클래스는 부모 클래스로부터 물려받은 메서드, 인스턴스/타입 프로퍼티, 서브스크립트 등을 그대로 사용하지 않고 자신만의 기능으로 변경하여 사용할 수 있다. 이것을 재정의 Override
라고 한다.
orverride 키워드
를 사용함- Swift 컴파일러가 슈퍼클래스(부모 포함 상위 부모 클래스)에 해당 특성이 있는지 확인 후 재정의함
- 만약, 재정의할 해당 특성이 없는데
override
키워드를 사용하면 컴파일 오류 발생 - 자식 클래스에서 부모클래스의 특성을 재정의 했을 때, 부모클래스 특성을 그대로 사용하고 싶다면
super
프로퍼티를 사용함 super
키워드를 타입 내에서 사용하면, 부모클래스의 타입 메서드 및 프로퍼티에 접근 가능super
키워드를 인스턴스 내에서 사용하면, 부모클래스의 인스턴스 메서드 및 프로퍼티, 서브스크립트에 접근 가능
overrride func someMethod() {
//error : 슈퍼클래스(superClass)에 재정의할 해당 메서드가 존재하지 않음
}
부모클래스 특성 호출/접근 방법
- 성공적으로 재정의한 부모 메서드인
someMethod()
를 호출하려면super.someMethod()
로 호출해야 함 - 재정의한
someProperty
의 부모 버전에 접근하려면super.someProperty
로 접근 가능 - 재정의한 서브스크립트에서 부모 버전의 서브스크립트에 접근하려면
super[index]
로 접근 가능
메서드 재정의
부모클래스로부터 상속받은 인스턴스 메서드 또는 타입 메서드를 자식클래스에서 용도에 맞게 재정의 가능하다.
아래 예제는 상속#1 게시글에서 예로 들었던 것을 그대로 가져와서 메서드를 재정의 했다.
하나하나 파헤쳐 가며 알아 보려 한다.
class Person {
//저장 프로퍼티
var name: String = ""
var age: Int = 0
//연산 프로퍼티
var introduction: String {
return "이름 : \(name). 나이 : \(age)"
}
//메서드
func speak() {
print("안녕하세요. 서근 개발노트 입니다!")
}
//연산 타입프로퍼티
//static 키워드가 아닌 class 키워드를 사용함.
class func introduceClass() -> String {
return "오늘도 즐거운 빡코딩!"
}
}
일단 슈퍼클래스인 Person
에 저장 프로퍼티, 연산 프로퍼티, 메서드 그리고 연산 타입 프로퍼티를 정의했다. Person
이 클래스 이기 때문에 연산 프로퍼티에는 static
가 아닌 class
키워드를 사용하여 메서드를 구현했다.
//자식클래스
class Student: Person {
enum grade:String {
case A = "통과 입니다."
case B = "재시험 입니다."
case F = "탈락 입니다."
}
func study(score: grade) {
print("\(score.rawValue)")
}
// 자식클래스에서 부모클래스의 speak()메서드 재정의
override func speak() {
print("안녕하세요! 저는 학생 입니다.")
}
}
자식클래스인 Student
는 Person
슈퍼클래스를 상속받았고, 프로퍼티, 메서드를 정의함과 동시에 부모클래스에서 사용한 speak()
메서드를 재정의 했다.
//자식클래스
class AnotherStudent: Student {
//연산 타입 프로퍼티의 경우에는 class 키워드를 사용해
//서브클래스가 슈퍼 클래의 의 구현부를 재정의(override)할 수 있다.
class func introduceClass() {
print(super.introduceClass())
}
override class func introduceClass() -> String {
return "코딩은 재밌으면서 어렵습니다.."
}
override func speak() {
print("안녕하세요! 저는 익명의 어느 학생입니다.")
}
}
AnotherStudent
자식클래스는 Student
클래스를 상속받았고, 슈퍼클래스의 메서드인 introduceClass
에 접근했는데, 연산 타입 메서드에 접근하기 위해 class
키워드를 사용했다. 그리고 슈퍼클래스를 호출하기 위해 super
프로퍼티를 사용했다.
여기서 이상한 점이 있는데 override
키워드가 붙은 introduceClass
메서드가 하나 더 있는 이유는 반환 타입이 다르기 때문이다. (타입 캐스팅 as 연산자에 대해 알아보시려면 여기를 클릭해주세요)
마지막으로 슈퍼클래스의 speak()
를 자식클래스에서 재정의 하기 위해 super
프로퍼티를 사용했다.
반환 타입 또는 매개변수가 다른 메서드
Swift는 메서드의 반환 타입이나 매개변수가 다르면 서로 다른 메서드로 취급해, 서로 다른 타입의 반환 값을 받아 오기 위해 타입 캐스팅 as
연산자를 사용해야 한다.
이제 각 클래스를 호출해보면
//부모클래스 인스턴스
let seogun: Person = Person()
seogun.speak() //안녕하세요. 서근 개발노트 입니다!
print(Person.introduceClass()) //오늘도 즐거운 빡코딩!
//자식클래스 인스턴스
let mijin: Student = Student()
mijin.speak() //안녕하세요! 저는 학생 입니다.
print(Student.introduceClass()) //오늘도 즐거운 빡코딩!
//자식클래스 인스턴스
let cheolsu: AnotherStudent = AnotherStudent()
cheolsu.speak() //안녕하세요! 저는 익명의 어느 학생입니다.
// 타입 캐스팅 as 연산자
print(AnotherStudent.introduceClass() as String) //코딩은 재밌으면서 어렵습니다..
AnotherStudent.introduceClass() as Void //오늘도 즐거운 빡코딩!
프로퍼티 재정의
- 부모클래스로부터 상속받은 인스턴스 프로퍼티 및 타입 프로퍼티를 자식 클래스에서 재정의 가능
- 프로퍼티 재정의 시, 저장 프로퍼티로 재정의 할 수는 없음
- 프로퍼티 재정의는 그 자체가 아닌 프로퍼티 접근자(
Getter
), 설정자(Setter
), 옵저버 등을 재정의 한다는 의미임 - 슈퍼클래스에서 저장 프로퍼티로 정의한 프로퍼티 및 연산 프로퍼티 또한 접근자와 설정자로 재정의 가능
- 프로퍼티를 상속받은 자식클래스에서는 슈퍼클래스의 프로퍼티 종류(저장인지 연산자인지)를 알지 못하고, 타입만 알 수 있음
- 프로퍼티 재정의 시 슈퍼클래스 프로퍼티 이름 및 타입이 일치해야 함
- 만약, 재정의할 해당 특성이 없는데
override
키워드를 사용하면 컴파일 오류 발생 - 슈퍼클래스에서 읽기 전용 프로퍼티였어도 자식클래스에서 읽고, 쓰기 가능한 프로퍼티로 재정의 가능
- 하지만 읽기 쓰기 모두 가능했던 프로퍼티를 읽기 전용으로만 재정의 불가(
get
,set
모두 재정의해야 함) - 접근자에 기능 변경이 필요 없다면
super
키워드를 사용해 부모클래스의 접근자를 사용해 값을 받아 반환하면 됨
class Person {
//저장 프로퍼티
var name: String = ""
var age: Int = 0
//연산 프로퍼티
var koreanAge: Int {
return self.age + 1
}
var introduction: String {
return "이름 : \(name). 나이(만) : \(age)"
}
}
class Student: Person {
var grade: String = "B"
//부모클래스의 연산 프로퍼티 재정의
override var introduction: String {
return super.introduction + " " + "성적 : \(grade)"
}
//부모클래스 읽기전용 이였던 연산 프로퍼티를 읽기/쓰기 모두 가능하도록 재정의
override var koreanAge: Int {
get {
return super.koreanAge
}
set {
self.age = newValue - 1
}
}
}
Student
자식클래스에서 Person
클래스에서 상속받은 introduction
과 koreanAge
연산 프로퍼티를 모두 재정의 해줬고, 읽기 전용 연산 프로퍼티를 읽기/쓰기 모두 가능하도록 재정의 했다.
이제 호출을 다음과 같이 해줄 수 있다.
let seogun: Person = Person()
seogun.name = "서근"
seogun.age = 21
//Value of type 'Person' has no member 'KoreanAge'
seogun.koreanAge = 20 //error: 읽기전용 연산 프로퍼티는 재설정 불가
print(seogun.introduction) //이름 : 서근. 나이(만) : 21
print(seogun.koreanAge) //22
let mijin: Student = Student()
mijin.name = "미진"
mijin.age = 19
//mijin.KoreanAge = 20 자식클래스에서 접근자를 통해 슈퍼클래스 재정의 가능
mijin.grade = "A"
print(mijin.introduction) //이름 : 미진. 나이(만) : 19 성적 : A
print(mijin.koreanAge) //20
프로퍼티 옵저버 재정의
- 프로퍼티 옵저버도 접근자 설정자처럼 재정의 가능함
- 슈퍼클래스에서 정의한 프로퍼티가 연산이던 저장 프로퍼티던 상관없음
- 상수 저장 프로퍼티나 읽기 전용(
getter
) 연산 프로퍼티는 재정의 불가.(상수 저장 및 읽기 전용 연산 프로퍼티는willSet
/didSet
메서드를 사용한 프로퍼티 옵저버를 원칙적으로 사용할 수 없음) - 프로퍼티 옵저버를 재정의 해도, 슈퍼클래스에 정의한 프로퍼티 옵저버 또한 같이 동작한다.
- 프로퍼티의 접근자와 옵저버는 동시에 재정의 불가(원한다면 재정의하는 접근자에 프로퍼티 옵저버 역할을 구현해야 함)
class Person {
var name: String = ""
var age: Int = 0 {
didSet {
print("Person(\(self.name))의 나이 : \(self.age)")
}
}
var koreanAge: Int {
return self.age + 1
}
var fullName: String {
get {
return self.name
}
set {
self.name = newValue
}
}
}
class Student: Person {
//부모클래스 저장 프로퍼티의 프로퍼티 감시자를 재정의
override var age: Int {
didSet {
print("Student(\(self.name))의 나이 : \(self.age)")
}
}
//kroeanAge 및 fullName 연산 프로퍼티 감시자 정의
override var koreanAge: Int {
get {
return super.koreanAge
}
set {
self.age = newValue - 1
}
// didSet { } //접근자와 옵저버를 동시에 재정의 불가
}
override var fullName: String {
didSet {
print("Full Name: \(self.fullName)")
}
}
}
부모클래스의 age
저장 프로퍼티 옵저버를 자식클래스에서 재정의 해줬고, koreanAge
및 fullName
연산 프로퍼티의 옵저버를 재정의 했다. 부모클래스에서 age
에 이미 옵저버를 정의했으므로 자식클래스 인스턴스에 age
프로퍼티의 값을 할당할 때, 부모클래스와 자식클래스의 age
감시자가 동시에 출력된다.
koreanAge
를 자식 클래스에서 프로퍼티 설정자를 재설정을 해줬는데 didSet
옵저버를 동시에 정의할 수 없다. 그래서 오류!
이제 호출을 다음과 같이 해줄 수 있다.
let seogun: Person = Person()
seogun.name = "서근"
seogun.age = 22
//Person(서근)의 나이 : 22
seogun.fullName = "서근 개발노트"
print(seogun.koreanAge) // 23
let mijin: Student = Student()
mijin.name = "미진"
mijin.age = 20
//Person의 옵저버와 Student의 옵저버가 동시에 실행
//Person(미진)의 나이 : 20
//Student(미진)의 나이 : 20
mijin.koreanAge = 21
mijin.fullName = "김미진" //Full Name: 김미진
print(mijin.koreanAge) //21
재정의 방지
- 자식클래스에서 재정의 불가하도록 제한할 수 있음
final 키워드
를 사용- 재정의를 방지한 특성을 자식클래스에서 재정의 하려 하면 컴파일 오류
- 클래스를 상속하거나 재정의 할 수 없게 하려면
class
앞에final
키워드 사용(자식클래스를 가질 수 없음) - 상속이 방지된 클래스를 상속받으려고 하면 컴파일 오류
class Person {
final var name: String = ""
final func speak() {
print("안녕하세요 \(self.name) 입니다.")
}
}
final class Student: Person {
// Person의 프로퍼티 및 메서드는 재정의 방지로 인해 재정의 불가
override var name: String {
get {
return "학생"
}
set {
super.name = newValue
}
}
override func speak() {
print("서근 블로그 입니다!")
}
}
// Student 클래스는 재정의 방지로 인해 상속 받을 수 없음
class AnotherClass: Student { }
// 자식클래스 컴파일 오류로 인해 출력 불가
let seogun: Person = Person()
seogun.name = "서근"
seogun.speak() //안녕하세요 서근 입니다.
읽어주셔서 감사합니다 🤟
'SWIFT > Grammar' 카테고리의 다른 글
Swift : 기초문법 [타입 캐스팅(Type Casting)] (1) | 2022.01.24 |
---|---|
Swift : 기초문법 [상속#3 - 클래스의 이니셜라이저 convenience, required] (0) | 2022.01.24 |
Swift : 기초문법 [상속#1 - 클래스 상속(자식, 부모, 기반 클래스)] (0) | 2022.01.22 |
Swift : 기초문법 [서브스크립트(subscript)] (0) | 2022.01.21 |
Swift: 기초문법 [모나드 - 컨텍스트, 함수객체, 모나드] (0) | 2022.01.20 |