Swift: Swift継承とポリモーフィズム:オブジェクト指向プログラミングの中核概念
継承はオブジェクト指向プログラミングの3本柱の一つで、あるクラスが別のクラスを基に構築され、既存の動作を再利用・拡張することを可能にします。このレッスンではSwiftの継承メカニズムとポリモーフィズムの応用を深く掘り下げます。
1. 学習目標
- 継承によるサブクラスの作成
overrideによるメソッド、プロパティ、サブスクリプトのオーバーライドsuperによる親クラス実装の呼び出しfinalによる継承とオーバーライドの防止- アップキャストとポリモーフィズムのランタイムメカニズム
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 */ }
}
frame、backgroundColor、render() は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) サブクラス化
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) ポリモーフィズム
ポリモーフィズムにより、スーパークラス型の変数がサブクラスのインスタンスを参照でき、サブクラスのオーバーライド実装に動的にディスパッチします:
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がアクセスする前にすべてのストアドプロパティが確定的な値を持つことが保証され、未初期化状態を防ぎます。
📖 まとめ
- Swiftは単一継承のみサポート — サブクラスはちょうど1つのスーパークラスから継承できる
overrideキーワードはスーパークラスのメソッド、コンピューテッドプロパティ、サブスクリプトをオーバーライドするsuperはオーバーライド内でスーパークラスの実装を呼び出すfinalはクラスのサブクラス化やメソッド/プロパティのオーバーライドを防止する- ポリモーフィズムによりスーパークラス参照がサブクラスのオーバーライド実装を呼び出せる — 「1つのインターフェース、多数の動作」
- サブクラスの初期化は2段階で行われる:まずサブクラスのストアドプロパティ、次にスーパークラス
📝 練習問題
- 基本:
Person基本クラス(プロパティ:name、age;メソッド:introduce())を作成し、introduce()をオーバーライドして独自のプロパティ(studentId、subject)を追加するStudentとTeacherサブクラスを作成してください。 - 中級:
titleプロパティとplay()メソッドを持つMediaItem基本クラスを設計してください。play()をオーバーライドするMovie(追加:director)とSong(追加:artist)サブクラスを作成してください。ポリモーフィック配列を使ってそれらをすべて再生してください。 - 発展: 「権限制御システム」を実装してください:
permissionLevelプロパティとcanAccess(_:)メソッドを持つ基本クラスUser。AdminUser、EditorUser、ViewerUserサブクラスを作成してください。AdminUserは常に true を返すfinalメソッドoverride func canAccess(_:)を使用します。ポリモーフィック配列を使って��ユーザーのアクセス権を統一的にチェックしてください。