공식 문서로 공부하는 Swift (12) - 상속

ci·2020년 5월 29일
1

Inheritance

클래스는 다른 클래스로부터 메소드, 프로퍼티 또는 다른 특성을 상속받을 수 있다. 한 클래스가 다른 클래스를 상속할 때, 상속 받는 클래스를 자식 클래스(서브 클래스, subclass)라 하고, 상속하는 클래스를 부모 클래스(수퍼 클래스, superclass)라고 부른다. 상속(inheritance)은 Swift의 다른 타입으로부터 클래스를 차별화하는 중요한 행위이다.

Swift의 클래스는 부모 클래스의 메소드, 프로퍼티, 서브스크립트를 호출하거나 거기에 접근할 수 있다. 그리고 오버라이딩을 통해 그들만의 버전으로 재정의할 수 있다. Swift는 오버라이드한 정의가 부모 클래스의 정의와 일치하는지 확인함으로써 오버라이드가 정확한지 보장한다.

클래스는 상속된 프로퍼티에 프로퍼티의 값이 변경됐을 때 알리기 위한 프로퍼티 옵저버를 더할 수 있다. 프로퍼티 옵저버는 저장 프로퍼티나 계산 프로퍼티에 상관없이 어떤 프로퍼티에도 더해질 수 있다.



기반 클래스 정의하기

다른 클래스를 상속하지 않는 클래스를 기반 클래스(base class)라고 부른다.

Swift의 클래스는 공통의 기반 클래스를 상속하지 않는다. 부모 클래스를 정하지 않은 클래스는 자동적으로 기반 클래스가 된다.


아래의 예시는 Vehicle 기반 클래스를 정의한다. Vehicle 클래스는 기본 값이 0.0인 currentSpeed 프로퍼티를 선언한다. 이 프로퍼티의 값은 읽기 전용 계산 프로퍼티인 description에서 차량을 묘사하기 위해 사용된다.

Vehicle 클래스는 makeNoise 메소드를 갖고 있는데, 이 메소드는 기본 Vehicle 인스턴스에서 어떤 행동도 하지 않는다. 나중의 Vehicle 클래스의 자식 클래스에서 커스터마이징될 것이다.

class Vehicle {
    var currentSpeed = 0.0
    var description: String {
        return "traveling at \(currentSpeed) miles per hour"
    }
    func makeNoise() {
        // do nothing - an arbitrary vehicle doesn't necessarily make a noise
    }
}

새로운 Vehicle 인스턴스를 만든 뒤에, 차량의 속도를 사람이 읽을 수 있게 묘사하는 description 프로퍼티에 접근할 수 있게 된다.

let someVehicle = Vehicle()

print("Vehicle: \(someVehicle.description)")
// Vehicle: traveling at 0.0 miles per hour


서브클래싱 (Subclassing)

서브클래싱(subclassing)은 기존 클래스를 기반으로 새로운 클래스를 만드는 것이다. 자식 클래스는 기존 클래스로부터 재정의할 수 있는 특성을 상속한다. 새로운 특성을 자식 클래스에 더할 수도 있다.

자식 클래스가 부모 클래스를 가졌다는 것을 알리기 위해, 자식 클래스 이름 뒤에 부모 클래스 이름을 콜론(:)으로 구분하여 넣는다.

class SomeSubclass: SomeSuperclass {
    // subclass definition goes here
}

class Bicycle: Vehicle {
    var hasBasket = false
}

새로운 Bicycle 클래스는 자동적으로 Vehicle의 모든 특성을 얻는다. 상속하는 특성에 더해 Bicycle 클래스는 새로운 저장 프로퍼티인 hasBasket을 정의한다. 이 프로퍼티는 false를 기본 값으로 갖는다.

let bicycle = Bicycle()
bicycle.hasBasket = true

Bicycle 인스턴스는 상속한 프로퍼티인 currentSpeed를 수정할 수도 있다.

bicycle.currentSpeed = 15.0
print("Bicycle: \(bicycle.description)")
// Bicycle: traveling at 15.0 miles per hour

TandemVehicle의 메소드와 프로퍼티를 포함하여 Bicycle의 모든 메소드와 프로퍼티를 상속한다. Tandem 자식 클래스는 새로운 저장 프로퍼티인 currentNumberOfPassengers를 추가할 수 있다.

class Tandem: Bicycle {
    var currentNumberOfPassengers = 0
}

Tandem의 인스턴스를 만든다면 상속한 프로퍼티와 새로 만든 프로퍼티에 다 접근할 수 있고, Vehicle의 읽기 전용인 desciption 프로퍼티에 질의를 할 수 있다.

let tandem = Tandem()
tandem.hasBasket = true
tandem.currentNumberOfPassengers = 2
tandem.currentSpeed = 22.0
print("Tandem: \(tandem.description)")
// Tandem: traveling at 22.0 miles per hour


오버라이딩 (Overriding)

자식 클래스는 부모 클래스로부터 상속한 인스턴스 메소드, 타입 메소드, 인스턴스 프로퍼티, 타입 프로퍼티, 서브스크립트를 커스터마이징하여 구현할 수 있다. 이를 오버라이딩(overriding)이라 부른다.

override 키워드로 오버라이딩을 정의한다. 오버라이드 할 의도를 명확히 하고, 실수에 의해 정의를 매칭하지 않도록 해야 한다. 잘못된 오버리이딩은 예상치 못한 행동을 유발한다. override 키워드가 없는 모든 오버라이드는 코드를 컴파일 할 때 에러로 진단된다.

override 키워드는 Swift 컴파일러가 오버라이딩하는 클래스의 부모 클래스를 확인하도록 한다. 이러한 체크는 오버라이딩 정의가 정확하단 것을 보장한다.


부모 클래스의 메소드, 프로퍼티, 서브스크립트에 접근하기

자식 클래스에 메소드, 프로퍼티, 서브스크립트를 제공할 때, 오버라이드의 일부분으로써 부모 클래스의 기존 구현을 사용하는 게 유용하기도 하다. 예를 들어 기존 구현의 행위를 재정의할 수 있으며, 수정된 값을 상속한 변수에 저장할 수도 있다.

super 접두사를 사용해 부모 클래스의 프로퍼티, 메소드, 서브스크립트에 접근할 수 있다.

  • 오버라이드 된 메소드 someMethod()는 오버라이딩 메소드 안에서 super.someMethod() 메소드를 호출해 someMethod()의 부모 클래스 버전을 사용할 수 있다.
  • 오버라이드 된 프로퍼티 someProperty는 오버라이딩 게터/세터에서 super.someProperty를 사용해 someProperty의 부모 클래스 버전에 접근할 수 있다.
  • 오버라이드 된 서브스크립트 someIndex는 오버라이딩 서브스크립트 구현에서 super[someIndex]를 통해 부모 클래스 버전에 접근할 수 있다.

메소드 오버라이딩

자식 클래스 안에서 메소드의 맞춤형 구현을 제공하거나 대체함으로써 상속한 인스턴스 메소드 혹은 타입 메소드를 오버라이딩 할 수 있다.

아래 예시는 Vehicle의 새로운 자식 클래스인 Train을 정의한다. makeNoise() 메소드는 Train에서 오버라이드 된다.

class Train: Vehicle {
    override func makeNoise() {
        print("Choo Choo")
    }
}

Train의 새로운 인스턴스를 만들고 makeNoise() 메소드를 호출하면, 자식 클래스 버전의 메소드가 실행되는 것을 볼 수 있다.

let train = Train()
train.makeNoise()
// Prints "Choo Choo"

프로퍼티 오버라이딩

프로퍼티에 커스텀 게터/세터를 제공하거나 프로퍼티 옵저버를 더함으로써 인스턴스 프로퍼티 혹은 타입 프로퍼티를 오버라이드 할 수 있다.


프로퍼티 게터/세터 오버라이딩

구현된 프로퍼티가 저장 프로퍼티인지 상속 프로퍼티인지 여부에 상관없이, 상속한 프로퍼티를 오버라이드 하기 위한 커스텀 게터/세터를 제공할 수 있다. 상속한 프로퍼티의 게터/세터는 자식 클래스에 알려져 있지 않다. 자식 클래스는 오직 상속한 프로퍼티가 가진 이름과 타입만 알고 있다. 오버라이딩 할 때 이 이름과 타입을 모두 항상 명시해야 한다. 컴파일러가 오버라이드 하는 프로퍼티와 부모 클래스의 프로퍼티가 같은 이름과 타입인지 확인해야 하기 때문이다.

자식 클래스에서 오버라이드 할 때 게터와 세터를 모두 제공함으로써 읽기 전용 프로퍼티를 읽기/쓰기가 다 가능한 프로퍼티로 상속할 수 있다. 하지만 읽기와 쓰기가 모두 되는 프로퍼티를 읽기 전용 프로퍼티로 상속할 수는 없다.

프로퍼티 오버라이드의 일부분으로써 세터를 제공할 경우 반드시 오버라이드를 위한 게터도 제공해야 한다. 상속한 프로퍼티의 값을 게터에서 수정하고 싶지 않다면 super.someProperty를 반환하여 간단히 넘겨 버릴 수 있다.


다음 예시는 Vehicle의 새로운 자식 클래스인 Car를 정의한다. Car 클래스는 새로운 저장 프로퍼티인 gear를 사용한다. Car 클래스는 Vehicle로부터 description 프로퍼티를 상속해 오버라이드 한다.

class Car: Vehicle {
    var gear = 1
    override var description: String {
        return super.description + " in gear \(gear)"
    }
}

let car = Car()
car.currentSpeed = 25.0
car.gear = 3
print("Car: \(car.description)")
// Car: traveling at 25.0 miles per hour in gear 3

프로퍼티 옵저버 오버라이딩

상속한 프로퍼티에 프로퍼티 옵저버를 더하는 오버라이딩을 할 수 있다. 상속한 프로퍼티의 값이 변할 때마다 알려 주게 된다. 프로퍼티가 원래 어떻게 구현되었는지는 중요하지 않다.

상수 저장 프로퍼티나 읽기 전용 계산 프로퍼티를 오버라이드 한 경우에는 프로퍼티 옵저버를 더할 수 없다. 이런 프로퍼티의 값은 설정되지 못하고, 오버라이드의 일부분으로써 willSet이나 didSet을 제공하지 못하기 때문이다.

같은 프로퍼티에 오버라이딩 세터와 오버라이딩 프로퍼티 옵저버를 둘 다 제공할 수 없다. 만약 프로퍼티 값의 변화를 관찰하고자 하는데 이미 프로퍼티에 커스텀 세터를 제공 중이라면, 커스텀 세터 안에서 값의 변화를 관찰할 수 있다.


아래 예시는 Car의 새로운 자식 클래스인 AutomaticCar를 정의한다.

class AutomaticCar: Car {
    override var currentSpeed: Double {
        didSet {
            gear = Int(currentSpeed / 10.0) + 1
        }
    }
}

let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print("AutomaticCar: \(automatic.description)")
// AutomaticCar: traveling at 35.0 miles per hour in gear 4


오버라이드 방지

final 키워드를 사용해 메소드, 프로퍼티, 서브스크립트가 오버라이드 되는 것을 방지할 수 있다. (final var, final func, final class func, and final subscript와 같이 작성한다). final로 선언된 메소드, 프로퍼티, 서브스트링을 오버라이드 하려고 시도하면 컴파일 에러가 발생한다.

클래스 전체를 final로 선언해서 클래스 안의 모든 메소드, 프로퍼티 등이 override가 되는 것을 막을 수 있다.

0개의 댓글