Swiftでプログラミング。- Inheritance 継承
クラスは、他のクラスからメソッド、プロパティ、その他の特性を継承することができます。あるクラスが他のクラスを継承すると、継承するクラスはサブクラスとして知られ、継承するクラスはスーパークラスとして知られます。継承は、Swiftで違うクラスと区別するための基本的な構文です。
Swiftのクラスは、そのスーパークラスに属するメソッド、プロパティ、およびサブスクリプトを呼び出してアクセスすることができ、それらのメソッド、プロパティ、およびサブスクリプトの独自のオーバーライドバージョンを提供して、その動作を洗練または変更することができます。Swiftは、オーバーライドの定義が一致するスーパークラスの定義を持つことをチェックすることで、オーバーライドが正しいことを確認するのに役立ちます。
クラスは、プロパティの値が変更されたときに通知を受けるために、継承したプロパティにプロパティオブザーバを追加することもできます。プロパティオブザーバは、それが元々保存プロパティや計算プロパティとして定義されていたかどうかに関わらず、任意のプロパティに追加することができます。
Defining a Base Class
他のクラスを継承していないクラスは、ベースクラスと呼ばれる。
Swiftのクラスは、普遍的なベースクラスを継承しません。スーパークラスを指定せずに定義したクラスは、自動的にベースクラスとなり、その上に構築することができます。
以下の例では、Vehicle というベースクラスを定義しています。この基本クラスは、currentSpeedと名付けた保存プロパティで、デフォルト値は0.0です(Double型)。currentSpeedプロパティの値は、読み取り専用のString型計算プロパティであるdescriptionに適応され、Vehicleの説明が作成されます。
また、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の新しいインスタンスを作成します。
let someVehicle = Vehicle()
新しいVehicleインスタンスを作成すると、そのDescriptionプロパティにアクセスして、車両の現在の速度を人間が読める形で表示することができます。
print("Vehicle: \(someVehicle.description)")
// Vehicle: traveling at 0.0 miles per hour
Vehicleクラスは、任意の車両に共通する特徴を定義していますが、それだけではあまり意味がありません。より便利に使うためには、より具体的な種類の車両を記述するように改良する必要があります。
Subclassing サブクラス化
サブクラス化とは、既存のクラスをベースにして新しいクラスを作ることです。サブクラスは、既存のクラスから特性を継承し、それを改良することができます。また、サブクラスに新しい特性を追加することもできます。
サブクラスにスーパークラスがあることを示すには、スーパークラス名の前にサブクラス名をコロンで区切って記述します。
class SomeSubclass: SomeSuperclass {
// subclass definition goes here
}
次の例では、Bicycleというサブクラスを定義し、Vehicleというスーパークラスを定義しています。
class Bicycle: Vehicle {
var hasBasket = false
}
新しいBicycleクラスは、currentSpeedやdescriptionプロパティ、makeNoise()メソッドなど、Vehicleのすべての特性を自動的に取得します。
継承した特性に加えて、Bicycleクラスは、新しい保存プロパティhasBasketを定義し、デフォルト値はfalseです(プロパティの型はBool)。
デフォルトでは、作成した新しい Bicycle インスタンスはバスケットを持ちません。インスタンス作成後に、特定のBicycleインスタンスのhasBasketプロパティをtrueに設定することができます。
let bicycle = Bicycle()
bicycle.hasBasket = true
また、Bicycleインスタンスの継承したcurrentSpeedプロパティを変更したり、インスタンスの継承したdescriptionプロパティを照会したりすることができます。
bicycle.currentSpeed = 15.0
print("Bicycle: \(bicycle.description)")
// Bicycle: traveling at 15.0 miles per hour
サブクラスは、それ自体をサブクラス化することができます。次の例では、「タンデム」と呼ばれる2人乗りの自転車用にBicycleのサブクラスを作成しています。
class Tandem: Bicycle {
var currentNumberOfPassengers = 0
}
TandemはBicycleからすべてのプロパティとメソッドを継承し、BicycleはVehicleからすべてのプロパティとメソッドを継承しています。Tandemサブクラスには、currentNumberOfPassengersという新しいストアドプロパティが追加され、デフォルト値は0になっています。
Tandem のインスタンスを作成すると、その新規および継承したプロパティを操作することができ、Vehicle から継承した読み取り専用の description プロパティを照会することができます。
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
サブクラスは、スーパークラスから継承したインスタンス・メソッド、タイプ・メソッド、インスタンス・プロパティ、タイプ・プロパティ、またはサブスクリプトの独自のカスタム実装を提供できます。これはオーバーライドと呼ばれます。
継承される特性を上書きするには、オーバーライド定義の前に override キーワードを付けます。そうすることで、オーバーライドを明確になります。誤ってオーバーライドすると、予期しない動作を引き起こす可能性があり、overrideキーワードを忘れて実装するとコードのコンパイル時にエラーとして診断されます。
overrideキーワードはまた、オーバーライドするクラスのスーパークラス(またはその親の1つ)が、オーバーライドのために提供されたものと一致する宣言をチェックします。このチェックは、オーバーライド定義が正しいことを保証します。
Accessing Superclass Methods, Properties, and Subscripts
サブクラスのメソッド、プロパティ、またはサブスクリプトのオーバーライドを提供する場合、オーバーライドの一部として既存のスーパークラスの実装を使用すると便利な場合があります。たとえば、その既存の実装の動作を改良したり、既存の継承された変数に変更された値を格納したりすることができます。
このような場合は、super 前置詞を使用して、メソッド、プロパティ、またはサブスクリプトのスーパークラスにアクセスします。
someMethod()という名前のオーバーライドされたメソッドは、オーバーライドされたメソッドの実装内でsuper.someMethod()を呼び出すことで、someMethod()のスーパークラスを呼び出すことができます。
somePropertyというオーバーライドされたプロパティは、オーバーライドされたゲッターまたはセッターの実装内でsuper.somePropertyとしてsomePropertyのスーパークラスにアクセスできます。
someIndex のオーバーライドされた添え字は、オーバーライドされた添え字の実装内で super[someIndex] として同じ添え字のスーパークラスにアクセスできます。
Overriding Methods
継承されたインスタンスまたはタイプのメソッドをオーバーライドすることで、サブクラス内でそのメソッドをカスタマイズまたは代替して実装することができます。
次の例では、Vehicle の新しいサブクラス「Train」を定義し、Train が Vehicle から継承した makeNoise() メソッドをオーバーライドしています。
class Train: Vehicle {
override func makeNoise() {
print("Choo Choo")
}
}
Trainの新しいインスタンスを作成し、そのmakeNoise()メソッドを呼び出すと、Trainサブクラス版のメソッドが呼び出されていることがわかります。
let train = Train()
train.makeNoise()
// Prints "Choo Choo"
Overriding Properties
継承されたインスタンスやタイプのプロパティをオーバーライドして、そのプロパティに独自のゲッターやセッターを提供したり、プロパティオブザーバーを追加して、オーバーライドするプロパティが基本となるプロパティの値が変更されたときに観察できるようにすることができます。
Overriding Property Getters and Setters
継承されたプロパティが保存プロパティまたは計算プロパティとして実装されているかどうかに関わらず、カスタムゲッター(および必要に応じてセッター)を提供して、継承されたプロパティをオーバーライドすることができます。継承されたプロパティの保存またはコ計算の性質は、サブクラスでは知ることができません。オーバーライドするプロパティの名前と型の両方を必ず明記し、オーバーライドが同じ名前と型を持つスーパークラスのプロパティと一致するかどうかをコンパイラがチェックできるようにしなければなりません。
サブクラスのプロパティのオーバーライドでゲッターとセッターの両方を提供することで、継承された読み取り専用のプロパティを読み取り・書き込み可能なプロパティとして提示することができます。ただし、継承されたリードライト・プロパティをリードオンリー・プロパティとして提示することはできません。
プロパティのオーバーライドの一部としてセッターを提供する場合は、そのオーバーライドにゲッターも提供する必要があります。継承したプロパティの値をオーバーライドのゲッター内で変更したくない場合は、super.somePropertyをゲッターから返すことで、継承した値を単純に渡すことができます(somePropertyはオーバーライドするプロパティの名前です)。
次の例では、VehicleのサブクラスであるCarという新しいクラスを定義しています。また、CarクラスはVehicleから継承したDescriptionプロパティをオーバーライドし、現在のギアを含むカスタム記述を提供しています。
class Car: Vehicle {
var gear = 1
override var description: String {
return super.description + " in gear \(gear)"
}
}
descriptionプロパティのオーバーライドは、まずsuper.descriptionを呼び出し、Vehicleクラスのdescriptionプロパティを返します。Carクラスのdescriptionは、このdescriptionの最後に、現在のギアに関する情報を提供するためのテキストを追加します。
Carクラスのインスタンスを作成し、そのgearプロパティとcurrentSpeedプロパティを設定すると、そのdescriptionプロパティが、Carクラス内で定義された調整済みの記述を返すことがわかります。
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
Overriding Property Observers
プロパティオーバーライドを使用すると、継承したプロパティにプロパティオブザーバを追加することができます。これにより、継承したプロパティの値が変更されたときに、そのプロパティが元々どのように実装されていたかに関わらず、通知を受けることができます。
継承した定数格納プロパティや継承した読み取り専用計算プロパティには、プロパティ・オブザーバを追加することはできません。これらのプロパティの値は設定することができないため、オーバーライドの一部としてwillSetやdidSetの実装を提供することは適切ではありません。
また、同じプロパティに対してオーバーライド セッターとオーバーライド プロパティ オブザーバーの両方を提供することはできません。プロパティの値の変更を監視したい場合、すでにそのプロパティのカスタムセッターを提供していれば、カスタムセッターの中から値の変更を監視することができます。
次の例では、Car のサブクラスである AutomaticCar という新しいクラスを定義しています。AutomaticCarクラスは、現在の速度に基づいて使用する適切なギアを自動的に選択する、自動変速機を備えた車を表しています。
class AutomaticCar: Car {
override var currentSpeed: Double {
didSet {
gear = Int(currentSpeed / 10.0) + 1
}
}
}
AutomaticCarインスタンスのcurrentSpeedプロパティを設定するたびに、プロパティのdidSetオブザーバーは、インスタンスのgearプロパティを新しい速度に適したギアに設定します。具体的には、プロパティオブザーバーは、新しいcurrentSpeedの値を10で割って最も近い整数に切り捨てた値に1を加えたギアを選択します。速度が35.0の場合、ギアは4になります。
let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print("AutomaticCar: \(automatic.description)")
// AutomaticCar: traveling at 35.0 miles per hour in gear 4
Preventing Overrides
メソッド、プロパティ、または添え字を final としてマークすることで、オーバーライドされないようにすることができます。これを行うには、メソッド、プロパティ、または添え字のイントロデューサ・キーワードの前に final 修飾子を記述します (final var、final func、final class func、final 添え字など)。
サブクラスで final メソッド、プロパティ、または添え字をオーバーライドしようとすると、コンパイル時エラーとして報告されます。拡張機能のクラスに追加したメソッド、プロパティ、または添え字は、拡張機能の定義内で final としてマークすることもできます。
クラス定義の class キーワードの前に final 修飾子を記述することで、クラス全体を final としてマークすることができます (final class)。final クラスをサブクラス化しようとすると、コンパイル時にエラーが発生します。