궁금한 내용을 검색해보세요!
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
서근 개발노트
티스토리에 팔로잉
SWIFT/Grammar

Swift : 기초문법 [상속#2 재정의 override]

서근
QUOTE THE DAY

-
Written by SeogunSEOGUN

반응형

본 게시글은 yagom님의 Swift 프로그래밍 3판을 참고하여 작성되었습니다.

 

 

재정의 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("안녕하세요! 저는 학생 입니다.")
    }
}

자식클래스인 StudentPerson 슈퍼클래스를 상속받았고, 프로퍼티, 메서드를 정의함과 동시에 부모클래스에서 사용한 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 프로퍼티를 사용했다.

TIP
 
 

반환 타입 또는 매개변수가 다른 메서드
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 클래스에서 상속받은 introductionkoreanAge 연산 프로퍼티를 모두 재정의 해줬고, 읽기 전용 연산 프로퍼티를 읽기/쓰기 모두 가능하도록 재정의 했다. 

 

이제 호출을 다음과 같이 해줄 수 있다.

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 저장 프로퍼티 옵저버를 자식클래스에서 재정의 해줬고, koreanAgefullName 연산 프로퍼티의 옵저버를 재정의 했다. 부모클래스에서 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() //안녕하세요 서근 입니다.

 

 

읽어주셔서 감사합니다 🤟

 

 

 


잘못된 내용이 있으면 언제든 피드백 부탁드립니다.


서근


위처럼 이미지 와 함께 댓글을 작성할 수 있습니다.