委譲と継承
コンポジションを選択します
「委譲と継承」はCoddyKit上の無料Kotlin Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはKotlin Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Kotlin Academyコースには全4レッスンが含まれています。
再利用する2つの方法
継承と委譲は、どちらも振る舞いを再利用する方法です。継承は、あるクラスが別のクラスの一種であることを表します。一方、委譲は、クラスが処理を転送する協力オブジェクトを保持することを表します。
よく知られた原則は、継承よりもコンポジションを優先することです。
継承の例
継承では、サブクラスが基底クラスを拡張し、メンバーをオーバーライドします。Kotlinでは、基底クラスにopenを付ける必要があります。
open class Animal {
open fun sound() = "..."
}
class Dog : Animal() {
override fun sound() = "woof"
}
fun main() {
println(Dog().sound())
}委譲の例
委譲でも同じように再利用できます。動作は、クラスが処理を委譲するメンバーに存在します。
interface Sound { fun sound(): String }
class Bark : Sound { override fun sound() = "woof" }
class Dog(s: Sound) : Sound by s
fun main() {
println(Dog(Bark()).sound())
}壊れやすい基底クラス
継承では、サブクラスが親クラスの内部実装に強く結び付きます。基底クラスを変更すると、サブクラスが気付かないうちに壊れることがあります。これが壊れやすい基底クラスの問題です。委譲はインターフェースにのみ依存します。
カプセル化
サブクラスは protected メンバーにアクセスしてフックをオーバーライドできるため、親クラスの内部を覗き見ることになります。委譲するクラスから見えるのはパブリックインターフェースだけなので、カプセル化が保たれます。
interface Counter { fun increment(): Int }
class SimpleCounter : Counter {
private var n = 0
override fun increment(): Int { n++; return n }
}
class GuardedCounter(private val c: Counter) : Counter by c {
override fun increment(): Int {
println("incrementing")
return c.increment()
}
}
fun main() {
val g = GuardedCounter(SimpleCounter())
println(g.increment())
println(g.increment())
}実行時の柔軟性
継承では、親クラスがコンパイル時に固定されます。委譲では、実行時に異なる実装を注入できるため、フェイクを使ったテストも簡単に行えます。
interface Clock { fun now(): Int }
class RealClock : Clock { override fun now() = 100 }
class FakeClock : Clock { override fun now() = 0 }
class Timer(c: Clock) : Clock by c
fun main() {
println(Timer(RealClock()).now())
println(Timer(FakeClock()).now()) // easy to test
}複数の動作
クラスが拡張できる親クラスは1つだけですが、多数のインターフェースに委譲することはできます。委譲を使うと、複数の機能を組み合わせやすくなります。
interface Walk { fun walk() = "walking" }
interface Swim { fun swim() = "swimming" }
class Walker : Walk
class Swimmer : Swim
class Amphibian : Walk by Walker(), Swim by Swimmer()
fun main() {
val a = Amphibian()
println(a.walk() + ", " + a.swim())
}継承が適している場合
真の is-a 関係があり、拡張を前提に設計された安定した階層構造を使う場合は、今でも継承が適しています。たとえば、sealed 型のファミリー、フレームワークの基底クラス、共通の抽象型に対するポリモーフィズムなどです。
sealed class Shape
class Circle(val r: Double) : Shape()
class Square(val s: Double) : Shape()
fun area(shape: Shape) = when (shape) {
is Circle -> 3.14 * shape.r * shape.r
is Square -> shape.s * shape.s
}
fun main() {
println(area(Circle(2.0)))
}委譲が適している場合
次のことを実現したい場合は、委譲を選択します。
- 無関係な型の間で動作を再利用する
- 複数の機能を組み合わせる
- 結合度を低く保ち、テストを簡単にする
- 既存の実装をラップしてデコレートする
比較
継承:親クラスは1つ、強い結合、コンパイル時に固定、is-a。委譲:協調オブジェクトは複数、疎結合、実行時に差し替え可能、has-a。Kotlinでは by によって委譲を低コストで利用できるため、設計の重心はコンポジションに傾きます。
実践的なデフォルト
よい経験則は、まず委譲から始めることです。本物で安定した is-a 階層が現れた場合にだけ、継承へ切り替えます。これにより、設計の柔軟性が保たれ、変更にも強くなります。
理解度チェック
委譲と継承についての理解度を確認しましょう。
まとめ
2つのアプローチを比較しました。
- 継承:is-a、単一の親クラス、強い結合
- 委譲:has-a、複数の協調オブジェクト、疎結合
- コンポジションを優先し、安定した is-a 階層には継承を使用する
次は、委譲を使った実践的なデコレーターパターンです。
よくある質問
「委譲と継承」レッスンは無料ですか?
はい。「委譲と継承」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Kotlin Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Kotlin Academyコースには全4レッスンが含まれています。
「委譲と継承」で何を学びますか?
コンポジションを選択します ブラウザで直接実行するハンズオンコードでKotlin Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Kotlin Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのKotlin Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「委譲と継承」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このKotlin Academyレッスンでコードを書いて実行できますか?
はい。すべてのKotlin Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。