Swiftのクラス継承(Inheritance)

Swiftのクラス継承(Inheritance)

継承(Inheritance)です。継承はクラス特有の機能で、構造体や列挙型では使えません。そういう意味では、継承はクラスをクラスたらしめている機能の1つです。

ここでは、Swiftにおける継承の定義構文から始めて、基底クラス、継承元のクラス(スーパークラス)と派生先のクラス(サブクラス)、さらにオーバーライドについて詳しく説明していきます。

継承(Inheritance)って何?

プログラミングにおける継承(Inheritance)とはなんでしょうか?英語の「inherit」とは「受け継ぐ」とか「引き継ぐ」という意味の動詞で、「inheritance」はその名詞形です。Swiftにおける継承とは

あるクラスが、メソッド、プロパティ、及びその他の特性を、別のクラスから引き継ぐこと

です。ここから分かるように、継承とはクラス特有の機能です。したがって、構造体や列挙型には、継承という機能はついていません。

継承の定義構文 | スーパークラス(Superclass)とサブクラス(Subclass)

あるクラスClassAの特長を「継承」した別のクラスClassBを作る場合、

// 継承の定義構文
class ClassB: ClassA {
  // クラスの定義
}

のように書きます。Swiftの継承では、継承したい元のクラス(ClassA)を、コロン:で分けて、継承させたい派生先クラス(ClassB)の後ろに書きます。

公式マニュアルでは、派生先クラス名直後にコロン(半角スペースなし)で、コロンの後ろに半角スペースを置く書き方(ClassB: ClassA)に統一されています。文法的には、スペースなしでも、スペースを空けすぎてもコンパイルは通ります。

Swiftでは、派生先のクラスをSubclass(サブクラス)、派生元になるクラスをSuperclass(スーパークラス)と呼びます。

繰り返しになりますが、上記のコードを日本語で書くと

サブクラスClassBは、スーパークラスClassAを継承している

と読めます。

なぜ継承する必要があるのか?

主に2つ利点があって、1つは「再利用」で、もう1つは「置換」です。

再利用というのは、継承の定義から自明かと思います。重要なのでもう一度書きますが、継承とは「あるクラスが、別のクラスの特性を引き継ぐこと」です。したがって、継承先のクラスは(基本的には)、元のクラスの機能を全て再利用できます。例えば、共通に使えるプロパティやメソッド等を実装した基本的なクラスを作っておけば、それを継承することでコードの省エネができるかもしれません。

もう1つの「置換」というのは、サブクラス(継承先)のインスタンスをスーパークラス(継承元)の型として取り扱い、インターフェイスの共用を可能にすることです。例えば、

// インターフェイスの共用
class A {
}

class B: A {
}

func someFunction(par: A) {
    // 共通のメソッドを実行
    // 実装が継承先クラスで異なる
}

のように、関数(またはメソッド)のパラメータにクラスインスタンスをセットした場合を考えます。もし、このクラス(A)を継承したサブクラス(B)を作った場合、オーバーライド(overriding)を使用することで、渡すインスタンスによって実装を動的に変えることが可能です。

オーバーライド(overriding)というのは、継承した特性を派生先で書き換えることです。オーバーライドは後で詳しく説明しますが、オーバーライドを使うことによって「共通のインターフェイスを使って、異なる実装を提供」することが可能になります。

基底クラス(Base Class)とは?

基底クラス(ベースクラス、Base Class)とは、一言で言うと

他のクラスを継承していないクラス

です。Baseは「基準」とか「基礎」、「土台」という意味ですから、基底クラスはその言葉通り「基礎」となるクラスです。

公式マニュアルにも注意書きがありますが、Swiftのクラス群は共通の基底クラスを継承していません。したがって、私達が作る(作った)新しいクラスは、もし別のクラスを継承していなければ、自動的に基底クラスになります。

“NOTE

Swift classes do not inherit from a universal base class. Classes you define without specifying a superclass automatically become base classes for you to build upon.”

抜粋:: Apple Inc. “The Swift Programming Language”。 iBooks https://itun.es/jp/jEUH0.l

基底クラスの具体例

公式マニュアルにも載っている「形(Shape)」クラスを使います。同じだと面白くないので、プロパティとして辺の数(number of sides)ではなく、頂点の数(number of vertices)を使います。

// 形の基底クラス
class Shape {
    // 頂点の数
    var numberOfVertices: Int = 0

    // 面積(サブクラスで実装)
    var area: Double {
        return 0.0
    }

    func description() -> String {
        return "\(numberOfVertices)個の頂点を持っています。面積は\(area)です。"
    }
}

この基底クラスShape格納プロパティ(stored property)であるnumberOfVerticesを持っていて、0で初期化されています。また、面積を計算する計算プロパティ(computed property)area、頂点の数と面積などの文字列を表示するインスタンスメソッドdescription()があります。

基底クラスは雛形、実装はサブクラス、という使い方が出来る

実際にクラスを使うときは、インスタンスを生成して、そのプロパティやメソッドにアクセスします。

// Shapeクラスインスタンス
let shape = Shape()
print(shape.description())
//"0個の頂点を持っています。面積は0.0です。"と表示

今、基底クラスは「形」(図形と言ってもいいかもしれません)を作るための共通特性として、「頂点の数」を保有しています。また、面積を返す読み出し専用計算プロパティ(read-only computed property)を保有していますが、中身が実装されていません。これは、後で紹介するオーバーライドを使い、サブクラス先で実装します。

次のセクションでは、実際にサブクラスを作って、形クラスにもっと具体的な機能を実装してみようと思います。

サブクラス(Subclass)を作る | Subclassing

サブクラス(Subclass)はスーパークラスの機能を引き継ぎます。後で説明するオーバーライドという機能を使うと、その機能の上書きが可能です。もちろん、サブクラス独自の特性(プロパティやメソッド等)を持たせることも可能です。

先程のShapeクラスを継承したサブクラスを作ってみます。

// 名前付き形クラス
class NamedShape: Shape {
    var name: String = ""

    override func description() -> String {
        return "名前は\(name)、" + super.description()
    }
}

let namedShape = NamedShape()
namedShape.name = "named shape"
print(namedShape.description())
//"名前はnamed shape、0個の頂点を持っています。面積は0.0です。"

格納プロパティとして名前nameを文字列で追加しています。さらに、後述するオーバーライドを使って、メソッドの機能を上書きしています。これくらいなら、サブクラスとして定義する必要はないかもしれませんが、今は説明のため敢えて継承させています。

サブクラスを派生させることもできる

継承元(つまりスーパークラス)は基底クラスでないといけない、というルールは当然ありません。したがって、サブクラス自体を継承させて、別のサブクラスを作ることが可能です。

試しに、NamedShapeを派生した、円クラスCircleを作ってみます。

// 円
class Circle: NamedShape {
    // 半径
    var radius: Double = 0.0
}

let circle = Circle()
circle.name = "test circle"
circle.radius = 2.0
print(circle.radius)
//"2.0"と表示

円の大きさを特徴づけるのは「半径(radius)」ですので、Doubleでプロパティとして追加しました。簡単のため、他の機能は追加していませんので、実用性はありませんが、一応「ある半径を持つ円」を表現できています。また、円は頂点がありませんから、numberOfVertices0のままです。

初期化子(initializer)」を使うともっとうまく書けると思いますが、別記事で紹介します。

まとめ

  • 派生元のクラスをスーパークラス(Superclass)、派生先のクラスをサブクラス(Subclass)と呼ぶ
  • 継承の定義構文はclass Subclass: Superclassのように書く
  • 他のクラスを継承していないクラスを基底クラス(Base Class)と呼ぶ
  • サブクラスを継承して、別のサブクラスを作ることも可能