LoginSignup
3
2

More than 3 years have passed since last update.

【Kotlin】「委譲による実装」と、外部から操作されないための工夫

Last updated at Posted at 2019-04-27

概要

  • あるインターフェイスを実装するクラスを作成する際に「委譲による実装」を利用すると楽になることがある。
  • ただし公式リファレンスにある基本のやり方そのままだと、クラス内に隠蔽されるべきオブジェクトを、クラスの外から操作できてしまう場合がある。
  • この問題はセカンダリコンストラクタを使った工夫をすることで回避できる。

課題

Fizz Buzz を行うクラスを実装してみましょう。
次のようなクラスです。

  • List<String> インターフェイスを実装しており、["1", "2", "Fizz", "4", "Buzz", ...] というような要素を持つ。
  • 要素は最初は空であり、next() を呼び出すごとに1つずつ増える。
    []next()["1"]next()["1", "2"]next()→ ...

使用例は次のようになります。

fun main() {
    val fizzBuzz = FizzBuzz()
    repeat(15) { fizzBuzz.next() } // 15までの要素を生成する。
    fizzBuzz.forEach { print("$it, ") } // > 1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz,
}

どのように実装するとよいでしょうか。

あらかじめ、与えられた数値を Fizz Buzz した文字列に変換する関数だけ作っておきましょう。

fun fizzBuzz(number: Int): String = when {
    number % 3 == 0 && number % 5 == 0 -> "FizzBuzz"
    number % 3 == 0 -> "Fizz"
    number % 5 == 0 -> "Buzz"
    else -> number.toString(10)
}

失敗例

案1

List<String> を実装しとるクラスを継承したろ!」

class FizzBuzz1 : ArrayList<String>() { // List<String> インターフェイスを実装した ArrayList<String> クラスを継承。
    fun next() {
        this += fizzBuzz(size + 1)
    }
}

「あかん、よそから勝手な値を突っ込めてまう」

fun main() {
    val fizzBuzz = FizzBuzz1()
    fizzBuzz += "FooBar"
    fizzBuzz.forEach { print("$it, ") } // > FooBar, 
}

案2

「ほな、 List<String> インターフェイスを実装しよか」

class FizzBuzz2 : List<String> {
}

List<String> のメンバを全部自前で実装するんはさすがに面倒やな。なかで mutableListOf() 使(つこ)たろか」

class FizzBuzz2 : List<String> {
    private val mutableList: MutableList<String> = mutableListOf()
}

List<String> インターフェイスのメンバが呼ばれたら mutableList の同名のメンバを呼ぶようにすればええ。こないなふうに…」

class FizzBuzz2 : List<String> {
    private val mutableList: MutableList<String> = mutableListOf()

    override val size: Int
        get() = mutableList.size

    override fun get(index: Int): String = mutableList.get(index)

    ...

}

「…ってちょお待てや、何個あんねん!1 やっとれるかい!」

委譲による実装

こういうときに、「委譲による実装」機能が役に立ちます。

案3

class FizzBuzz3(
    private val mutableList: MutableList<String> // コンストラクタ引数で MutableList<String> のインスタンスを受け取る。
): List<String> by mutableList { // List<String> のメンバが呼び出されたら mutableList の同じメンバに処理を委譲することを宣言。
    fun next() {
        mutableList += fizzBuzz(size + 1)
    }
}

コンストラクタ引数で List<String> インターフェイスを継承した MutableList<String> のインスタンス mutableList を受け取り、
クラスが List<String> インターフェイスを実装していることを宣言しているところに by mutableList を付けています。
こうすることで、List<String> のメンバの実装として mutableList が使用されます。(案2でやろうとしたように。)

案3の欠点

上記は公式リファレンスにも書かれているやりかたです。

ですが…。
これは mutableList オブジェクトをコンストラクタ引数で受け取っています。
つまり、外から mutableList を、ひいては FizzBuzz3 オブジェクトを、操作することができてしまうのです。

fun main() {
    val mutableList = mutableListOf<String>()
    val fizzBuzz = FizzBuzz3(mutableList)
    mutableList += "FooBar"
    fizzBuzz.forEach { print("$it, ") } // > FooBar, 
}

(あと、使う側が mutableList を用意してやらないといけないのがめんどくさい。)

案4(完成)

これを解決するにはどうしたらよいか。

  • プライマリコンストラクタを private にし、
  • セカンダリコンストラクタで mutableList を生成してプライマリコンストラクタに渡しましょう。

(コンストラクタの書き方について詳しくない方はこちらをご覧ください。→【Kotlin】コンストラクタ − プライマリとセカンダリ

class FizzBuzz private constructor(
    private val mutableList: MutableList<String>
) : List<String> by mutableList {
    constructor() : this(mutableListOf())

    fun next() {
        mutableList += fizzBuzz(size + 1)
    }
}

これで mutableList をクラス内に隠蔽し、外部から操作されることがなくなりました!
(使う側が mutableList を用意する必要もなくなりました!)


  1. 11個 

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2