상속 1 (클래스 상속 / 메서드, 프로퍼티, 프로퍼티 감시자 / 재정의 방지)

Gooreum·2021년 11월 14일
0

Swift

목록 보기
15/16

상속

  • 다른 클래스로부터 상속을 받지 않은 클래스를 기반클래스(Base Class)라고 한다.
  • 상속은 기반클래스를 다른 클래스에서 물려받는 것을 말한다.
  • 클래스는 메서드프로퍼티 등을 다른 클래스로부터 상속 가능하다.
  • 상속은 스위프트의 다른 타입과 클래스를 구별 짓는 클래스만의 특징이다.
  • 스위프트의 클래스는 부모클래스로부터 물려받은 메서드를 호출할 수 있고 프로퍼티에 접근할 수 있으며 서브스크립트도 사용할 수 있다.
  • 자식클래스는 부모클래스의 프로퍼티, 메서드, 서브스크립트 등을 자신만의 내용으로 재정의할 수도 있다.
    • 이때 재정의한다는 것을 명확히 확인해주어야 한다.
  • 상속받은 프로퍼티에 프로퍼티의 값이 변경되었을 때 알려주는 프로퍼티 감시자도 구현할 수 있다.
    • 연산 프로퍼티를 정의해준 클래스에서는 연산 프로퍼티에 프로퍼티 감시자를 구현 할 수 없지만, 부모클래스에서 연산 프로퍼티로 정의한 프로퍼티든 저장 프로퍼티로 정의한 프로퍼티든 자식클래스에서는 프로퍼티 감시자를 구현할 수 있다.

재정의

  • 자식클래스는 부모클래스로부터 물려받은 특성을 그대로 사용하지 않고 자신만의 기능으로 변경하여 사용할 수 있는데, 이를 재정의(override)라고 한다.
  • 상속받은 특성들을 재정의하려면 새로운 정의 앞에 override 키워드를 사용한다.
    • 사용하지 않으면 컴파일 오류가 발생한다.
  • 만약 자식클래스에서 부모클래스의 특성을 재정의했을 때, 부모클래스의 특성을 자식클래스에서 사용하고 싶다면 super 프로퍼티를 사용하면 된다.
    • super 키워드를 메서드 내에서 사용한다면, 부모클래스의 타입 메서드와 타입 프로퍼티에 접근할 수 있으며 인스턴스 메서드 내에서 사용한다면, 부모클래스의 인스턴스 메서드와 인스턴스 프로퍼티, 서브스크립트에 접근 가능하다.

메서드 재정의

  • 부모클래스의 메서드를 재정의하기 위해선 메서드 앞에 override 키워드를 사용한다.
  • 부모클래스의 메서드를 재정의하더라도 부모클래스의 메서드 기능을 그대로 사용하고자 한다면 메서드 내부에서 super 프로퍼티를 사용하면 된다.
class Person {
    var name: String = ""
    var age: Int = 0
    
    var introduction: String {
        return "이름 : \(name), 나이 : \(age)"
    }
    
    func speak() {
        print("가나다라마바사")
    }
    
    class func introduceClass() -> String {
        return "인류의 소원은 평화입니다."
    }
}

class Student: Person {
    var grade: String = "F"
    
    func study() {
        print("Study hard")
    }
    
    override func speak() {
        print("저는 학생입니다.")
    }
}

class UniversityStudent: Student {
    var major: String = ""
    
    class func introduceClass() {
        print(super.introduceClass())
    }
    
    override class func introduceClass() -> String {
        return "대학생의 소원은 A+ 입니다."
    }
    
    override func speak() {
        super.speak()
        print("대학생이죠.")
    }
}

print("----------yagom----------")
let yagom : Person = Person()
yagom.speak()

print("----------jay----------")
let jay: Student = Student()
jay.speak()

print("----------jenny----------")
let jenny: UniversityStudent = UniversityStudent()
jenny.speak()

print("----------Person.introduceClass----------")
print(Person.introduceClass())
print("----------Student.introduceClass----------")
print(Student.introduceClass())
print("----------UniversityStudent.introduceClass as String----------")
print(UniversityStudent.introduceClass() as String)
print("----------UniversityStudent.introduceClass as Void----------")
UniversityStudent.introduceClass() as Void

--result--
----------yagom----------
가나다라마바사
----------jay----------
저는 학생입니다.
----------jenny----------
저는 학생입니다.
대학생이죠.
----------Person.introduceClass----------
인류의 소원은 평화입니다.
----------Student.introduceClass----------
인류의 소원은 평화입니다.
----------UniversityStudent.introduceClass as String----------
대학생의 소원은 A+ 입니다.
----------UniversityStudent.introduceClass as Void----------
인류의 소원은 평화입니다.
  • UniversityStudent 클래스에서 override가 있는 introduceClass() 메서드와 없는 introduceClass() 메서드를 살펴보자.
    • UniversityStudent 클래스는 Person 클래스를 상속받았기 때문에 introduceClass()를 상속받아 사용가능하다. Override가 붙은 introduceClass()는 해당 introduceClass()메서드를 재정의한 것이고, override가 없는 것은 단순히 메서드 명칭만 같을 뿐 반환 타입이 다르기 때문에 상속받은 메서드가 아니다.

프로퍼티 재정의

  • 부모클래스로부터 상속받은 인스턴스 프로퍼티나 타입 프로퍼티를 자식클래스에서 용도에 맞게 재정의 가능하다.
  • 프로퍼티를 재정의할 때는 저장 프로퍼티로 재정의할 수는 없다.
  • 프로퍼티를 재정의한다는 것은 프로퍼티 자체가 아니라 프로퍼티의 접근자(Getter), 설정자(Setter), 프로퍼티 감시자(Property Observer) 등을 재정의하는 것을 의미한다.
  • 조상클래스에서 저장 프로퍼티로 정의한 프로퍼티는 물론이고 연산 프로퍼티로 정의한 프로퍼티도 접근자와 설정자를 재정의할 수 있다.
    • 프로퍼티를 상속받은 자식클래스에서는 조상클래스의 프로퍼티 종류(저장, 연산 등)는 알지 못하고 단지 이름과 타입만을 알기 때문이다.
  • 조상클래스에서 읽기 전용 프로퍼티였더라도 자식클래스에서 읽고 쓰기가 가능한 프로퍼티로 재정의해줄 수도 있다.
    • 그러나 읽기 쓰기 모두 가능했던 프로퍼티를 읽기 전용으로 재정의해줄 수는 없다.
  • 읽기 쓰기가 모두 가능한 프로퍼티를 재정의할 때 접근자 설정자 모두 재정의해야 하며, 설정자만 재정의할 수는 없다.
    • 만약 접근자에 따로 기능 변경이 필요 없다면 super.someProperty와 같은 식으로 부모클래스의 접근자를 사용하여 값을 받아와 반환해주면 된다.
class Person {
    var name: String = ""
    var age: Int = 0
    var koreanAge: Int { //get 전용 연산프로퍼티
        return self.age + 1
    }
    var introduction: String {
        return "이름 : \(name), 나이 : \(age)"
    }
}

class Student: Person {
    var grade: String = "F"
    
    override var introduction: String {
        return super.introduction + ", " + "학점: \(self.grade)"
    }
    
    override var koreanAge: Int { //get set 연산프로퍼티로 재정의
        get {
            return super.koreanAge
        }
        set {
            self.age = newValue - 1
        }
    }
}

let yagom = Person()
yagom.name = "야곰"
yagom.age = 55
//yagom.koreanAge = 56 -> 컴파일 오류발생
print(yagom.introduction)
print(yagom.koreanAge)

let jay: Student = Student()
jay.name = "제이"
jay.age = 14
jay.koreanAge = 15
print(jay.introduction)
print(jay.koreanAge)
  • Person 클래스의 koreanAge 연산 프로퍼티는 읽기 전용이지만 자식클래스인 Student에서는 읽기-쓰기 연산프로퍼티로 재정의 하였습니다.
  • Student 클래스에서 koreanAge 연산 프로퍼티 재정의시 접근자(getter)는 따로 기능을 재정의할 필요가 없어 부모클래스의 property를 그대로 사용하기 위해 super.koreanAge 작성

프로퍼티 감시자 재정의

  • 프로퍼티 감시자도 프로퍼티의 접근자와 설정자처럼 재정의 가능하다.
  • 마찬가지로 조상클래스에 정의한 프로퍼티가 연산 프로퍼티인지 저장 프로퍼티인지는 상관없다. 다만 상수 저장 프로퍼티읽기 전용 연산 프로퍼티프로퍼티 감시자를 재정의 할 수 없다.
    • 왜냐하면 상수 저장 프로퍼티나 읽기 전용 연산 프로퍼티는 값을 설정할 수 없으므로 willSet이나 didSet 메서드를 사용한 프로퍼티 감시자를 원천적으로 사용할 수 없기 때문이다.
  • 프로퍼티 감시자를 재정의하더라도 조상클래스에 정의한 프로퍼티 감시자도 동작한다는 점을 잊지 말아야 한다.
  • 프로퍼티 접근자프로퍼티 감시자동시에 재정의할 수 없다. 만약 둘 다 동작하길 원한다면 재정의하는 접근자에 프로퍼티 감시자의 역할을 구현해야 한다.
class Person {
    var name: String = ""
    var age: Int = 0 {
        didSet {
            print("Person age: \(self.age)")
        }
    }
    var koreanAge: Int {
        return self.age + 1
    }
    var fullName: String {
        get {
            return self.name
        }
        set {
            self.name = newValue
        }
    }
}

class Student: Person {
    var grade: String = "F"
    
    override var age: Int {
        didSet {
            print("Student age: \(self.age)")
        }
    }
    
    override var koreanAge: Int {
        get {
            return super.koreanAge
        }
        set {
            self.age = newValue - 1
        }
        //didSet { } -> 오류발생
    }
    override var fullName: String {
        didSet {
            print("FullName: \(self.fullName)")
        }
    }
}

let yagom = Person()
yagom.name = "야곰"
yagom.age = 55
yagom.fullName = "조야곰"
print(yagom.koreanAge)

let jay: Student = Student()
jay.name = "제이"
jay.age = 14
jay.koreanAge = 15
jay.fullName = "김제이"
print(jay.koreanAge)

--result--
Person age: 55
56
Person age: 14
Student age: 14
Person age: 14
Student age: 14
FullName: 김제이
15
  • Student 클래스에는 Person 클래스의 age라는 저장 프로퍼티의 프로퍼티 감시자를 재정의 하였으며, koreanAge와 fullName이라는 연산 프로퍼티의 프로퍼티 감시자를 재정의.
  • Person 클래스의 age라는 저장 프로퍼티에 이미 프로퍼티 감시자를 정의했으므로 jay의 age 프로퍼티의 값을 할당할 때는 Person에 정의한 프로퍼티 감시자와 Student에 정의한 프로퍼티 감시자가 모두 동작한다.
  • 기존에 연산 프로퍼티로 정의했던 fullName 프로퍼티에도 프로퍼티 감시자를 정의하였다.
  • 그러나 koreanAge 프로퍼티에는 프로퍼티 감시자와 프로퍼티 설정자를 동시에 정의할 수 없다.

재정의 방지

  • 부모클래스를 상속받는 자식클래스에서 몇몇 특성을 재정의할 수 없도록 제한하고 싶다면 재정의를 방지하고 싶은 특성 앞에 final 키워드를 명시하면 된다.
    • final 키워드가 명시된 특성을 재정의 할 경우 컴파일 오류가 발생한다.
  • 클래스를 상속하거나 재정의할 수 없도록 하고 싶다면 class 키워드 앞에 final 키워드를 명시하면 된다.
profile
하루하루 꾸준히

0개의 댓글