Swift: Swift継承とポリモーフィズム:オブジェクト指向プログラミングの中核概念

継承はオブジェクト指向プログラミングの3本柱の一つで、あるクラスが別のクラスを基に構築され、既存の動作を再利用・拡張することを可能にします。このレッスンではSwiftの継承メカニズムとポリモーフィズムの応用を深く掘り下げます。

1. 学習目標


2. UI開発者の実話

(1) 課題:UIコンポーネント間の大量のコード重複

CharlieはButton、Label、TextFieldなどを含むUIコンポーネントライブラリを構築しています — すべて同様の基本動作を持ちます:

SWIFT
class Button {
    var frame: CGRect
    var backgroundColor: UIColor
    func render() { /* Draw background */ }
    func handleTap() { /* Handle tap */ }
}
class Label {
    var frame: CGRect
    var backgroundColor: UIColor
    func render() { /* Draw background */ }
}

framebackgroundColorrender() はButtonとLabelでほぼ同一です。新しいコンポーネントが増えるたびに同じプロパティをコピーアンドペーストし、基本動作の変更には10ファイルの編集が必要です。

(2) 解決策:継承

SWIFT
class UIView {
    var frame: CGRect
    var backgroundColor: UIColor
    func render() { /* Draw base background */ }
}
class Button: UIView {
    override func render() {
        super.render()          // First, draw background
        // Then draw button-specific styling
    }
    func handleTap() { }
}
class Label: UIView {
    override func render() {
        super.render()
        // Then draw label-specific styling
    }
}

共通プロパティは一度定義され、すべてのサブクラスで再利用されます。基本動作の変更には1ファイルの編集のみで済みます。

(3) 利点:再利用 + 拡張性

観点 コピーペーストスタイル 継承
コード再利用 0% — 各コンポーネント独立 100% — 基本プロパティとメソッド
変更コスト 10ファイル編集 1つの基本クラスを編集
新コンポーネント ファイル全体をコピー 継承 + 差分追加
一貫性 編集漏れが発生しやすい 基本クラスが一貫した動作を保証
拡張性 オーバーライドリスク高い override + super で制御可能

3. 継承の基本

(1) サブクラス化

100%
graph TB
    A[Animal] --> B["プロパティ: name, age"]
    A --> C["メソッド: makeSound()"]
    B --> D[Dog]
    C --> D
    D --> E["プロパティ: breed"]
    D --> F["オーバーライド: makeSound()"]
    A --> G[Cat]
    G --> H["プロパティ: color"]
    G --> I["オーバーライド: makeSound()"]
概念 説明
基本クラス / スーパークラス 継承元のクラス。Swiftではすべてのクラスが最終的に AnyObject から派生
サブクラス 基本クラスから継承するクラス。新しいプロパティとメソッドを追加可能
単一継承 Swiftは単一継承のみサポート(1サブクラス、1スーパークラス)
ルートクラス スーパークラスを持たないク��スをルートクラスと呼ぶ

▶ サンプル: 基本的な継承

SWIFT
// ============================================
// Basic inheritance usage
// ============================================
// Base class
class Vehicle {
    var make: String
    var model: String
    var year: Int
    init(make: String, model: String, year: Int) {
        self.make = make
        self.model = model
        self.year = year
    }
    func description() -> String {
        return "\(year) \(make) \(model)"
    }
    func startEngine() {
        print("Engine started")
    }
}
// Subclass — inherits from Vehicle
class Car: Vehicle {
    var numberOfDoors: Int
    init(make: String, model: String, year: Int, numberOfDoors: Int) {
        self.numberOfDoors = numberOfDoors
        super.init(make: make, model: model, year: year)  // Call superclass initializer
    }
    func honk() {
        print("Beep beep!")
    }
}
// Subclass — inherits from Vehicle
class Motorcycle: Vehicle {
    var hasSidecar: Bool
    init(make: String, model: String, year: Int, hasSidecar: Bool = false) {
        self.hasSidecar = hasSidecar
        super.init(make: make, model: model, year: year)
    }
    func wheelie() {
        print("Doing a wheelie!")
    }
}
let car = Car(make: "Toyota", model: "Camry", year: 2022, numberOfDoors: 4)
print(car.description())  // Inherited method
car.honk()                // Car's own method

出力:

TEXT 📖 参照専用
2022 Toyota Camry
Beep beep!

4. オーバーライドとsuper

(1) overrideによるオーバーライド

サブクラスは override を使ってスーパークラスのメソッド、コンピューテッドプロパティ、サブスクリプトをオーバーライドできます:

オーバーライド可能 override 必須 備考
インスタンスメソッド はい — 追加必須 新しい関数本体を提供
コンピューテッドプロパティ はい — 追加必須 新しいgetter/setterを提供
サブスクリプト はい — 追加必須 新しいサブスクリプト実装を提供
ストアドプロパティ いいえ — 不可 ストアドプロパティはオーバーライド不可

▶ サンプル: メソッドのオーバーライド

SWIFT
// ============================================
// Overriding superclass methods with override
// ============================================
class Animal {
    var name: String
    init(name: String) { self.name = name }
    func makeSound() {
        print("\(name) makes a sound")
    }
}
class Dog: Animal {
    override func makeSound() {
        print("\(name) barks: Woof!")
    }
}
class Cat: Animal {
    override func makeSound() {
        print("\(name) meows: Meow!")
    }
}
let dog = Dog(name: "Buddy")
let cat = Cat(name: "Whiskers")
dog.makeSound()
cat.makeSound()

出力:

TEXT 📖 参照専用
Buddy barks: Woof!
Whiskers meows: Meow!

(2) superによるスーパークラスの呼び出し

オーバーライド内で super を使ってスーパークラスのバージョンを呼び出します:

SWIFT
// ============================================
// Calling superclass with the super keyword
// ============================================
class Shape {
    var color: String
    init(color: String) {
        self.color = color
        print("Shape init: \(color)")
    }
    func draw() {
        print("Drawing a shape in \(color)")
    }
}
class Circle: Shape {
    var radius: Double
    init(color: String, radius: Double) {
        self.radius = radius
        super.init(color: color)  // Complete own init first, then call super
    }
    override func draw() {
        super.draw()  // First, execute superclass drawing
        print("  It's a circle with radius \(radius)")
    }
}
let circle = Circle(color: "Red", radius: 5.0)
circle.draw()

出力:

TEXT 📖 参照専用
Shape init: Red
Drawing a shape in Red
  It's a circle with radius 5

ヒント: 初期化時、サブクラスはまず自身のストアドプロパティの代入を完了してから super.init を呼び出す必要があります。これはSwiftの2段階初期化安全機構です。


5. finalとポリモーフィズム

(1) finalによる継承防止

final キーワードはクラスがサブクラス化されることや、メソッド/プロパティがオーバーライドされることを防ぎます:

使用法 効果
final class このクラスはサブクラス化不可
final func このメソッドはオーバーライド不可
final var このプロパティはオーバーライド不可

▶ サンプル: finalの使用

SWIFT
// ============================================
// final prevents inheritance and overriding
// ============================================
class Calculator {
    // Basic operations can be overridden
    func add(_ a: Int, _ b: Int) -> Int { return a + b }
    // Core logic must not be overridden
    final func process(_ a: Int, _ b: Int, operation: String) -> String {
        let result: Int
        switch operation {
        case "add": result = add(a, b)
        default: result = 0
        }
        return "Result: \(result)"
    }
}
// This subclass can override add, but not process
class AdvancedCalculator: Calculator {
    override func add(_ a: Int, _ b: Int) -> Int {
        print("Advanced add: \(a) + \(b)")
        return a + b
    }
    // override func process(...)  // Compile error!
}
let calc = AdvancedCalculator()
print(calc.process(3, 4, operation: "add"))

出力:

TEXT 📖 参照専用
Advanced add: 3 + 4
Result: 7

(2) ポリモーフィズム

ポリモーフィズムにより、スーパークラス型の変数がサブクラスのインスタンスを参照でき、サブクラスのオーバーライド実装に動的にディスパッチします:

100%
graph TB
    A["let shapes: [Shape] = [Circle(), Rectangle()]"] --> B["for shape in shapes"]
    B --> C["shape.draw()  ← ポリモーフィズム"]
    C --> D["Circleの draw()"]
    C --> E["Rectangleの draw()"]

▶ サンプル: ポリモーフィズムの実践

SWIFT
// ============================================
// Polymorphism — processing different types uniformly
// ============================================
class Employee {
    let name: String
    init(name: String) { self.name = name }
    func work() -> String {
        return "\(name) is working"
    }
    func bonus() -> Double {
        return 0
    }
}
class Developer: Employee {
    override func work() -> String { return "\(name) is writing code" }
    override func bonus() -> Double { return 5000 }
}
class Designer: Employee {
    override func work() -> String { return "\(name) is designing UI" }
    override func bonus() -> Double { return 3000 }
}
class Manager: Employee {
    override func work() -> String { return "\(name) is managing team" }
    override func bonus() -> Double { return 8000 }
}
// Polymorphism: process different subclasses uniformly
let team: [Employee] = [
    Developer(name: "Alice"),
    Designer(name: "Bob"),
    Manager(name: "Charlie")
]
var totalBonus = 0.0
for member in team {
    print(member.work())   // Dynamically calls each implementation
    totalBonus += member.bonus()
}
print("Total bonus: $\(totalBonus)")

出力:

TEXT 📖 参照専用
Alice is writing code
Bob is designing UI
Charlie is managing team
Total bonus: $16000.0

6. 完全な例:UIコンポーネントレンダリングシステム

SWIFT
// ============================================
// Complete example: UI component rendering system
// Features: Inheritance + overriding + super + final + polymorphism combined
// ============================================
import Foundation
// 1. Base class (subclassable)
class UIComponent {
    let id: String
    var x: Int
    var y: Int
    init(id: String, x: Int, y: Int) {
        self.id = id
        self.x = x
        self.y = y
    }
    func render() -> String {
        return "[\(id)] at (\(x), \(y))"
    }
    // Core logic — must not be overridden
    final func display() {
        print(render())
    }
}
// 2. Button subclass
class Button: UIComponent {
    let label: String
    init(id: String, x: Int, y: Int, label: String) {
        self.label = label
        super.init(id: id, x: x, y: y)
    }
    override func render() -> String {
        let base = super.render()
        return "\(base) [Button: \(label)]"
    }
}
// 3. TextField subclass
class TextField: UIComponent {
    var text: String
    init(id: String, x: Int, y: Int, text: String = "") {
        self.text = text
        super.init(id: id, x: x, y: y)
    }
    override func render() -> String {
        return "\(super.render()) [TextField: \"\(text)\"]"
    }
}
// 4. Polymorphic rendering
let components: [UIComponent] = [
    Button(id: "btn1", x: 10, y: 20, label: "Submit"),
    TextField(id: "txt1", x: 10, y: 60, text: "Hello"),
    Button(id: "btn2", x: 10, y: 100, label: "Cancel"),
    TextField(id: "txt2", x: 10, y: 140)
]
for component in components {
    component.display()  // Polymorphic call
}

出力:

TEXT 📖 参照専用
[btn1] at (10, 20) [Button: Submit]
[txt1] at (10, 60) [TextField: "Hello"]
[btn2] at (10, 100) [Button: Cancel]
[txt2] at (10, 140) [TextField: ""]

❓ よくある質問

Q Swiftは多重継承をサポートしていますか?
A いいえ。Swiftは単一継承言語です(1サブクラス、1スーパークラス)。多重継承のニーズはプロトコルで満たされます — クラスは複数のプロトコルに準拠できます。
Q メソッドをオーバーライドするとき、なぜsuperを呼び出さなければならないことがあるのですか?
A 必須ではありませんが、super を呼び出さないとスーパークラスのロジックが完全に置き換えられます。典型的なパターンは、まず super を呼び出して共通ロジックを実行し、次にサブクラス固有の動作を追加することです。super を呼び出すかどうかは設計意図によります。
Q final classとstructは継承防止に関して同じですか?
A 動作上は同じです — どちらもサブクラス化できません。ただし、structは値型で、classは参照型です。final class を使って参照セマンティクスを保ちつつ継承を防止します。継承も参照セマンティクスも不要な場合はstructを使います。
Q アップキャストとダウンキャストの違いは何ですか?
A アップキャストはサブクラスオブジェクトをスーパークラス型として扱います(自動的かつ暗黙的、例:Employee e = Developer(...))。ダウンキャストはスーパークラス参照をサブクラス型に戻します(as? または as! が必要で、変換が失敗する可能性があるため結果はオプショナルです)。
Q super.initの前に自身のストアドプロパティを初期化しなければならないのはなぜですか?
A これはSwiftの2段階初期化安全機構です。第1段階:サブクラスからスーパークラスに向けてストアドプロパティを初期化。第2段階:スーパークラスからサブク��スに向けてカスタマイズ。これにより、スーパークラスのinitがアクセスする前にすべてのストアドプロパティが確定的な値を持つことが保証され、未初期化状態を防ぎます。

📖 まとめ


📝 練習問題

  1. 基本: Person 基本クラス(プロパティ:nameage;メソッド:introduce())を作成し、introduce() をオーバーライドして独自のプロパティ(studentIdsubject)を追加する StudentTeacher サブクラスを作成してください。
  2. 中級: title プロパティと play() メソッドを持つ MediaItem 基本クラスを設計してください。play() をオーバーライドする Movie(追加:director)と Song(追加:artist)サブクラスを作成してください。ポリモーフィック配列を使ってそれらをすべて再生してください。
  3. 発展: 「権限制御システム」を実装してください:permissionLevel プロパティと canAccess(_:) メソッドを持つ基本クラス UserAdminUserEditorUserViewerUser サブクラスを作成してください。AdminUser は常に true を返す final メソッド override func canAccess(_:) を使用します。ポリモーフィック配列を使って��ユーザーのアクセス権を統一的にチェックしてください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%