継承とは親クラスの属性を引き継いで新たなクラスを作成することです。継承するクラスをサブクラス又は子クラス、継承されるクラスをスーバークラス又は親クラスと呼びます。
また、継承ツリーの起点となるクラスのことをベースクラスと呼びます。
Objective-Cでは、全てのクラスは、NSObject又は、NSObjectをベースクラスとするクラスを継承する必要がありますが、Swiftでは、ユーザ定義のクラスをベースクラスとすることができます。特定のクラスを継承する必要はありません。
サブクラス
別のクラスを継承したサブクラスを作成するには、クラスを定義する時にクラス名の後に:(コロン)をつけて、スーバークラス名を指定します。
/* モンスタークラス */
class Monster {
var name: String // 名前
var level: Int // レベル
// イニシャライザ
init(name: String, level: Int = 1) {
self.name = name
self.level = level
}
// ステータス表示
func printStatus() {
print("名前:\(name) レベル:\(level)")
}
// 攻撃
func attackMonster(enemy: Monster) {
print("\(name)は\(enemy.name)を攻撃した。");
}
}
/* スライムクラス */
class Slime: Monster {
// イニシャライザ
init(level: Int = 1) {
super.init(name: "スライム", level: level)
}
}
/* ドラゴンクラス */
class Dragon: Monster {
// イニシャライザ
init(level: Int = 1) {
super.init(name: "ドラゴン", level: level)
}
}
let monster = Monster(name: "踊るガイコツ", level:3)
let slime = Slime()
let dragon = Dragon(level: 10)
monster.printStatus() // 名前:踊るガイコツ レベル:3
slime.printStatus() // 名前:スライム レベル:1
dragon.printStatus() // 名前:ドラゴン レベル:10
superで、スーパークラスのメソッドやプロパティにアクセスできます。上の例ではイニシャライザでスーバークラスのイニシャライザを呼び出しています。スーバークラスのサブスクリプトにアクセスする場合は、super[index]の形でアクセスできます。
サブクラスをさらに継承してクラスを作ることもできます。
/* メタルスライムクラス */
class MetalSilme: Slime {
// イニシャライザ
override init(level: Int = 1) {
super.init(level: level)
self.name = "メタルスライム"
}
}
上のクラスで、イニシャライザの記述の前にoverrideが指定されています。イニシャライザに限らず、スーバークラスで既に定義されているメソッドと同じシグニチャ(同じ名前、同じ引数、同じ戻り値)のメソッドを定義する場合は、overrideを指定する必要があります。これは誤ってスーバークラスのメソッドを上書きしてしまうミスを防ぐためです。overrideを指定したメソッドと同じメソッドがスーバークラス(継承ツリーのどこか)に定義されていない場合もエラーになります。
Swiftでは、直接指定できる親クラスは1つだけです。C++言語等にある多重継承はできません。複数の特徴を併せ持つことを表現する場合は、プロトコル(Javaのインターフェースに相当)を使用します。
サブクラスに、新たなプロパティを追加することもできます。
/* メタルスライムクラス */
class MetalSilme: Slime {
var isStrayed: Bool // はぐれ?
// イニシャライザ
init(isStrayed: Bool, level: Int = 1) {
self.isStrayed = isStrayed
super.init(level: level)
if self.isStrayed {
self.name = "メタルスライム"
} else {
self.name = "はぐれメタル"
}
}
}
上の例では、isStrayというBool型のプロパティを追加しています。イニシャライザの引数も合わせて変更したことでシグニチャが変わったので、overrideの指定は不要になりました。
サブクラスのイニシャライザでは、このようにサブクラスの自身のプロパティの初期化をまず行い、次にスーパークラスのイニシャライザを呼び、その後に追加の設定を行うという流れになります。プロバティの初期化を行わずに、スーバークラスのイニシャライザを呼び出すとコンパイルエラーになります。
プロパティの宣言と同時にvar isStrayed = false 等と初期化をする場合はイニシャライザの中での初期化は不要です。
ポリモーフィズム
サブクラスでスーバークラスのメソッドをオーバーライドした場合、そのクラスのインスタンスをスーバークラスの型の変数に代入してスーバークラスのメソッドを呼び出しても、サブクラスのメソッドが呼び出されます。この性質をポリモーフィズム、又は日本語で多態性と呼びます。
次の例では、Monster型の変数に、Monsterクラスを継承したサブクラスのインスタンスを生成し、MonsterクラスのattackMonsterメソッドを呼び出していますが、実際に呼び出されるメソッドはサブクラスのメソッドです。(サブクラスでメソッドがオーバーライドされていなければ、スーバークラスのメソッドが呼び出されます。)
/* モンスタークラス */
class Monster {
var name: String // 名前
var level: Int // レベル
// イニシャライザ
init(name: String, level: Int = 1) {
self.name = name
self.level = level
}
// 攻撃
func attackMonster(enemy: Monster) {
println("\(name)は\(enemy.name)を攻撃した。");
}
}
/* スライムクラス */
class Slime: Monster {
// イニシャライザ
init(level: Int = 1) {
super.init(name: "スライム", level: level)
}
}
/* ドラゴンクラス */
class Dragon: Monster {
// イニシャライザ
init(level: Int = 1) {
super.init(name: "ドラゴン", level: level)
}
// 攻撃
override func attackMonster(enemy: Monster) {
println("\(name)は\(enemy.name)に火を吐いた。");
}
}
var skeleton, slime, dragon: Monster
skeleton = Monster(name: "踊るガイコツ")
slime = Slime()
dragon = Dragon()
skeleton.attackMonster(slime) // 踊るガイコツはスライムを攻撃した。 <== Monsterクラスのメソッドが呼ばれた
slime.attackMonster(skeleton) // スライムは踊るガイコツを攻撃した。 <== Slimeクラスのメソッドが呼ばれた
dragon.attackMonster(slime) // ドラゴンはスライムに火を吐いた。 <== Dragonクラスのメソッドが呼ばれた
例えば次のように、引数の異なるattackMonsterメソッドを追加すると、attackMonsterメソッドのオーバーロードになります。(オーバーロードに、overloadのような指定はありません。)
/* モンスタークラス */
class Monster {
:
// 攻撃
func attackMonster(enemy: Monster) {
println("\(name)は\(enemy.name)を攻撃した。");
}
// オーバーロード(引数の数が違う)
func attackMonster(enemy: Monster, times: Int) {
println("\(name)は\(enemy.name)を\(times)回攻撃した。");
}
}
/* スライムクラス */
class Slime: Monster {
:
// オーバーライド(スーバークラスのメソッドと同じシグニチャ)
override func attackMonster(enemy: Monster, times: Int) {
println("\(name)は\(enemy.name)に\(times)回ぶつかった。");
}
// オーバーロード(引数の型が違う)
func attackMonster(enemy: Dragon) {
println("\(name)は逃げ出した。");
}
}
var skeleton = Monster(name: "踊るガイコツ")
var dragon = Dragon()
var slime = Slime()
skeleton.attackMonster(slime, times:3) // 踊るガイコツはスライムを3回攻撃した。
slime.attackMonster(dragon) // スライムは逃げ出した。
※上の例では、SlimeクラスのattackMonsterメソッドの引数にDragonクラス型の変数に代入したインスタンスを渡しています。Monsterクラス型の変数にDragonのクラスインスタンスを代入したものを渡しても、スーパークラスのメソッドの方が呼ばれてしまいます。
var slime, dragon: Monster
slime = Slime()
dragon = Dragon()
slime.attackMonster(dragon) // スライムはドラゴンを攻撃した。
このようにインスタンスそのものではなく、引数自体の型で判断されます。
型キャストを使って、
slime.attackMonster(dragon as Dragon) // スライムは逃げ出した。
と明示的に呼び出す方法もありますが、型の違いを意識せずに引数を渡したい場合は、オーバーロードではなく、Slimeクラスのメソッドを次の様にオーバーライドする方法もあります。
// 攻撃
override func attackMonster(enemy: Monster) {
if enemy is Dragon {
println("\(name)は逃げ出した。");
} else {
super.attackMonster(enemy)
}
}
isは、変数が、指定した型、又は指定した型のサブクラスかどうかを判定してBool型を返す演算子です。
オーバーライド
インスタンスメソッドのオーバーライドは上の例でも出てきましたが、他にプロパティやサブスクリプト、クラスメソッド等のオーバーライドも可能です。例を次に示します。
/* モンスタークラス */
class Monster {
// 名前
var name: String
// レベル
var level: Int {
didSet {
maxHitPoint = level * 5
}
}
// ヒットポイント
var hitPoint: Int = 0 {
didSet {
if hitPoint <= 0 {
println("\(name)は死んだ")
}
}
}
// 最大ヒットポイント
var maxHitPoint: Int {
get {
return level * 5
}
set {
level = newValue / 5
}
}
// 死んだ?
var isDead: Bool {
return hitPoint <= 0
}
// イニシャライザ
init(name: String, level: Int = 1) {
self.name = name
self.level = level
self.hitPoint = self.maxHitPoint
}
// ダメージ計算
func calculateDamage(enemy: Monster) -> Int {
// 計算は適当
return max(0, (level * 5 - enemy.level) + (Int(rand()) % level))
}
// 攻撃
func attackMonster(enemy: Monster) {
var damage = self.calculateDamage(enemy)
println("\(name)は\(enemy.name)に\(damage)ポイントのダメージを与えた。")
enemy.hitPoint -= damage
}
}
/* ドラゴンクラス */
class Dragon: Monster {
override var level: Int {
didSet {
maxHitPoint = level * 100
}
}
override var maxHitPoint: Int {
get {
return level * 100
}
set {
level = newValue / 100
}
}
// イニシャライザ
init(level: Int = 1) {
super.init(name: "ドラゴン", level: level)
}
// ダメージ計算
override func calculateDamage(enemy: Monster) -> Int {
// 計算は適当
return max(0, (level * 100 - enemy.level) + (Int(rand()) % level))
}
}
var slime, dragon: Monster
slime = Monster(name: "スライム")
dragon = Dragon()
slime.attackMonster(dragon)
dragon.attackMonster(slime)
/* 実行結果
スライムはドラゴンに4ポイントのダメージを与えた。
ドラゴンはスライムに99ポイントのダメージを与えた。
スライムは死んだ
*/
MonsterクラスのhitPointとmaxHitPointをオーバーライドしています。メソッドの場合と同様にoverrideの指定が必要です。
上の例では、5と100というマジックナンバーがソースコードに複数出現しています。リファクタリングしてこの値をクラス変数にしてみます。
/* モンスタークラス */
class Monster {
// レベル係数
class var levelFactor: Int {
return 5
}
// 名前
var name: String
// レベル
var level: Int {
didSet {
maxHitPoint = level * self.dynamicType.levelFactor
}
}
// ヒットポイント
var hitPoint: Int = 0 {
didSet {
if hitPoint <= 0 {
println("\(name)は死んだ")
}
}
}
// 最大ヒットポイント
var maxHitPoint: Int {
get {
return level * self.dynamicType.levelFactor
}
set {
level = newValue / self.dynamicType.levelFactor
}
}
:
// イニシャライザ
init(name: String, level: Int = 1) {
self.name = name
self.level = level
self.hitPoint = self.maxHitPoint
}
// ダメージ計算
func calculateDamage(enemy: Monster) -> Int {
// 計算は適当
return max(0, (level * self.dynamicType.levelFactor - enemy.level) + (Int(rand()) % level))
}
:
}
/* ドラゴンクラス */
class Dragon: Monster {
// レベル係数
override class var levelFactor: Int {
return 100
}
init(level: Int = 1) {
super.init(name: "ドラゴン", level: level)
}
:
}
マジックナンバーをレベル係数というクラス変数で返すようにしたので、サブクラスでもクラス変数のオーバーライドだけで済むようになりました。
上で使っている、dynamicTypeは、指定したインスタンスの型を返すメソッドです。インスタンスのクラスからクラス変数を取得するために使っています。
オーバーライドの禁止
サブクラスによるメソッドやプロパティのオーバーライドを禁止することができます。オーバーライドを禁止するには、宣言の頭にfinalを指定します。
/* モンスタークラス */
class Monster {
:
// レベル
final var level: Int {
didSet {
maxHitPoint = level * self.dynamicType.levelFactor
}
}
// 最大ヒットポイント
final var maxHitPoint: Int {
get {
return level * self.dynamicType.levelFactor
}
set {
level = newValue / self.dynamicType.levelFactor
}
}
// ダメージ計算
final func calculateDamage(enemy: Monster) -> Int {
// 計算は適当
return max(0, (level * self.dynamicType.levelFactor - enemy.level) + (Int(rand()) % level))
}
:
}
/* スライムクラス */
class Slime: Monster {
// エラー
override var level: Int {
:
}
// エラー
override var maxHitPoint: Int {
:
}
// エラー
override func calculateDamage(enemy: Monster) -> Int {
:
}
}
クラス自体にfinalをつけて、継承を禁止することもできます。
/* モンスタークラス */
final class Monster {
:
}
// エラー
class Slime: Monster {
}