2020/1/17 Swift 継承(inheritance)

クラスはメソッド・プロパティ、そしてその他の特徴を他のクラスから継承できます。継承先のクラスはサブクラス、継承元(継承される)クラスはスーパークラスと呼ばれます。継承はSwiftにおいてクラスとその他の型を違うものとする基本的な挙動です。

Swiftのクラスは、スーパークラスのメソッド・プロパティ・サブスクリプトにアクセスしたり呼び出したりすることができ、スーパークラスのメンバの挙動を修正変更してオーバーライドすることもできます。Swiftはオーバーライドの定義が、スーパークラスの定義と矛盾しないかをチェックして、オーバーライドが正しく行われることをサポートします。

クラスは継承したプロパティの値が変更されたことを知らせるために、プロパティオブザーバーを追加することができます。オリジナルの定義がストアドプロパティかコンピューテドプロパティかは関係なく、全てのプロパティに対してプロパティオブザーバを追加できます。

ベースクラスを定義する

スーパークラスを持たないクラスは、ベースクラスとして知られています。

NOTE

Swiftのクラスはユニバーサルベースクラスを継承しません。スーパークラスを継承していることを明示しない場合そのクラスはベースクラスになります。

下のサンプルはベースクラスVehicleを定義しています。このベースクラスはデフォルト値0.0でストアドプロパティcurrentSpeedを定義しています。currentSpeedの型はDouble型と推定されます。currentSpeedプロパティの値は、読み取り専用String型プロパティのdescriptionで使用され、descriptionはその車の説明文を生成します。

ベースクラスVehicleは、makeNoiseメソッドも定義します。Vehicleインスタンスを生成してmakeNoiseメソッドを呼び出しても、makeNoiseメソッドは特に何もしません。後にVehicleクラスのサブクラスを作るときに、オーバーライドするためのメソッドとして用意しています。

sample1-1

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
    }
}

let someVehicle = Vehicle()

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

イニシャライザを用いて、新しいVehicleインスタンスを生成します。イニシャライザは型名(Vehicle)の後に空の丸かっこ()を記述します(sample1-1,11行目)。

インスタンスを生成したら、車の現在のスピードを表示するdescriptionプロパティにアクセスできます。

Vehicleクラスはあらゆる車の共通の特徴を定義しています。それはそれでいいのですが、これをさらに便利なものとするためには、それぞれの車に特有の特徴をさらに定義する必要があります。そのためにVehicleクラスを継承したサブクラスを定義します。

サブクラスを作る

サブクラスを作る、というのは、既存のクラスを基礎に置くクラスを作ることです。サブクラスは既存のクラスの特徴を継承して、それを改良することもできます。サブクラスに、スーパークラスには無い特徴を追加することもできます。

サブクラスがスーパークラスを持つことを示すために、以下のようにサブクラス名の後にコロン( : )で区切り、スーパークラス名を記述します。

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

下のサンプルはVehicleクラスのサブクラスとしてBicycleクラスを定義しています。

class Bicycle: Vehicle {
    var hasBasket = false
}

新しいBicycleクラスは、Vehicleクラスの全ての特徴を自動的に手に入れます。つまりスーパークラスのcurrentSpeedプロパティとdescriptionプロパティとmakeNoiseメソッドを継承します。

継承した特徴に加えて、Bicycleクラスは新しいストアドプロパティhasBasket(デフォルト値false)を定義しています。(sample2-1,17行目)hasBasketプロパティはBool型と推定されます。

初期値falseなので、新しく生成したBicycleクラスのインスタンスはバスケット(かご)を持っていません。インスタンス生成後hasBasketプロパティにtrueをセットすることはできます。

sample2-1

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
    }
}

let someVehicle = Vehicle()

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

class Bicycle: Vehicle {
    var hasBasket = false
}

let bicycle = Bicycle()
bicycle.hasBasket = true

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

継承したBicycleクラスのcurrentSpeedプロパティの値を変更することもできます。そして継承したdescriptionプロパティにアクセスすることもできます(23,24行目)。


サブクラスがサブクラスを持つこともあります。sample3-1ではBicycleクラスのサブクラスとして、2シーター自転車の「タンデム」を表すクラスを定義しています。

sample3-1

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
    }
}

class Bicycle: Vehicle {
    var hasBasket = false
}

class Tandem: Bicycle {
    var currentNumberOfPassengers = 0
}

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

TandemクラスはBicycleの全てのプロパティとメソッドを継承します。そしてBicycleクラスはVehicleクラスの全てのプロパティとメソッドを継承しています。そしてTandemクラスは新しいストアドプロパティcurrentNumberOfPassengersプロパティ(デフォルト値0)を追加しています。

Tandemクラスのインスタンスを生成したら、全ての継承したプロパティ、新しく導入したプロパティを扱うことができます。そしてVehicleクラスから継承した読み取り専用のdescriptionプロパティにアクセスすることもできます(sample3-1,23行目)。

オーバーライド

サブクラスは、

  • インスタンスメソッド
  • タイプメソッド
  • インスタンスプロパティ
  • タイププロパティ
  • サブスクリプト

スーパークラスから継承した上記のものを、独自に改造することができます。これはオーバーライドとして知られています。

継承した特徴をオーバーライドするには、オーバーライドの定義の前にoverrideキーワードを記述します。オーバーライドしていることをはっきり明示して、ミスによる同名の定義を防止します。ミスによるオーバーライドは予期せぬ挙動を引き起こします。そしてoverrideキーワードの無いオーバーライドはコンパイルによりエラーと診断されます。

さらにoverrideキーワードはSwiftコンパイラに、オーバーライドの定義とスーパークラスの定義の整合性のチェックを促します。これはオーバーライドの定義が正確に行われることの助けになります。

スーパークラスのメソッド・プロパティ・サブスクリプとにアクセスする

サブクラスにおいてメソッド・プロパティ・サブスクリプトをオーバーライドする時、既存のスーパークラスの実装をオーバーライドの一部として使うことは効果的です。既存の実装の動作を改良したり、既存の継承された変数を変更した値を保存したりできます。

これが適切な場合は、次のsuperプレフィックスを使用して、メソッド、プロパティ、またはサブスクリプトのスーパークラスバージョンにアクセスします。

●オーバーライドしたメソッドsomeMethod()は、その本体内から、スーパークラスのsomeMethod()を、

super.someMethod()

の記述で呼び出すことができます。

●オーバーライドしたプロパティsomePropertyはそのゲッタ・セッタ内からスーパークラスのsomePropertyに対し、

super.someProperty

の記述でアクセスできます。

●オーバーライドしたサブスクリプトsomeIndexは、そのサブスクリプトの実装の中で、スーパークラスのサブスクリプに対し、

super[someIndex]

の記述でアクセスできます。

オーバーライドするメソッド

sample4-1

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
    }
}

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

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

sample4-1ではVehicleクラスの新しいサブクラスTrainクラスを定義しています。Vehicleクラスから継承したmakeNoise()メソッドをオーバーライドしています。

Trainクラスの新しいインスタンスを生成し、makeNoiseメソッドを呼び出すと、サブクラスTrainクラスのmakeNoiseメソッドが呼び出されることを確認できます。


プロパティをオーバーライドする

継承したプロパティをオーバーライドするために、独自のゲッター(必要な時はセッターも)を提供することができます。継承元のプロパティの定義がストアドプロパティか、コンピューテドプロパティかは関係なくゲッター・セッターを提供できます。サブクラスでは、継承元のプロパティがストアドプロパティかコンピューテドプロパティかはわかりません。サブクラスでは継承元のプロパティの名前と型のみわかります。オーバーライドと継承元の実装の(名前と型の)整合性をコンパイラがチェックするため、プロパティの名前と型をはっきり示す必要があります。

継承元で読み取り専用プロパティとして定義されているものを、ゲッターとセッターを提供することで、サブクラスのオーバーライドで読み書き可能プロパティとしてオーバーライドすることができます。継承元で読み書き可能プロパティとして定義されているものを、読み取り専用プロパティにオーバーライドすることはできません。

NOTE

プロパティオーバーライドの際セッターを用意した場合、必ずゲッターも用意する必要があります。

 

sample5-1

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
    }
}

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

sample5-1ではVehicleクラスのサブクラスCarクラスを定義しています。Carクラスは新しいストアドプロパティgear(デフォルト値1)を導入しています。CarクラスはさらにVehicleクラスから継承したdescriptionプロパティを、現在のギアの値を含めて説明するコンピューテドプロパティにオーバーライドしています。

descriptionプロパティのオーバーライドの定義は

super.description

の呼び出しから始まっています。super.descriptionはVehicleクラスのdescriptionプロパティの結果を返します。Carクラスのdescriptionはそれに加えて追加の説明(現在のギアの値)を付け加えます。


プロパティオブザーバのオーバーライド

継承したプロパティにプロパティオブザーバを追加するために、プロパティのオーバーライドをすることができます。

 

NOTE

継承した

  • 定数のストアドプロパティ
  • 読み取り専用今ピューテドプロパティ

に、オーバーライドでプロパティオブザーバを追加することはできません。値の再セットができないので、追加する意味がないですね。

オーバーライドで同じプロパティに対して「セッター」と「プロパティオブザーバー」を同時に追加することはできません。すでにオーバーライドでセッターを追加したプロパティの値の変化を監視したい場合、セッター内に、値の変化の監視と変化したときに行われる処理を実装してください。

sample6-1

 

sample6-1では新しいクラスAutomaticCarを定義しています。Carクラスのサブクラスです。

 

 

 

参考

https://docs.swift.org/swift-book/LanguageGuide/Inheritance.html

コメントを残す

メールアドレスが公開されることはありません。