🦍

kotlinの拡張関数をmix-in的に使ってdecoratorパターンを実現する

2022/07/27に公開

動機は?

トップレベルで拡張関数を定義すると、アプリ内のどこからでもその拡張関数が使用できてしまいjavaにありがちな巨大utilsクラス(StringUtilsみたいな)と遜色ないのではないかと思っていて、もっと限定的なスコープで拡張関数を実装できないか考えていました。

色々試していたところこのような実装方法を思いつきました。

実装

フェードインアニメーション

androidにおけるViewのalphaを0f -> 1fと徐々に変化させるフェードインアニメーション

interface FadeInAnimation {
    fun View.animation() {
	// 省略
    }
}

トランスレーションアニメーション

androidにおけるViewのyをfrom -> toと徐々に変化させるトランスレーションアニメーション

interface TranslateAnimation {
    fun View.animation(from: Float, to: Float) {
	// 省略
    }
}

フェードインアニメーションView

class FadeInView: View, FadeInAnimation {
    fun fadeIn() {
        animation()
    }
}

フェードインしつつ移動アニメーションつきView

class FadeInTranslationView: View, FadeInAnimation, TranslateAnimation {
    fun show(to: Float) {
        fadeIn()
	translationY(to)
    }
    
    private fun fadeIn() {
        animation()
    }
    
    private fun translationY(to: Float) {
        animation(y, to)
    }
}

説明

interface内に拡張関数を定義することによって、そのinterfaceをimplementsしたクラス内でしか拡張関数を使用できませんでした。

これによって他のViewは影響を受けず限定的なスコープで拡張関数を利用でき、decoratorパターンが実現できるようになりました。

余談

自分で思いついたときは割といいアイデアのように思えましたが、下記のようなkotlinの仕様により同僚からは不評でした。

  • インスタンスメソッドに既に存在するシンタックスと拡張関数のシンタックスがバッティングした場合、拡張関数の定義はできるが実行時に無視されインスタンスメソッドが使用される
  • HogeクラスとFuga: HogeクラスがあったときにHoge.foo()とFuga.foo()を追加するとそれぞれ別の拡張関数が追加されたことになり、一般的なポリモーフィズムの振る舞いにならない
  • implementsした複数のinterface内に同一シンタックスの拡張関数が存在していた場合、implements先でその拡張関数のoverrideを強制される

今回のような使い方をするのがよくない、ではなく、そもそも拡張関数を乱用すべきではない、のような意見でした。

参考

https://dogwood008.github.io/kotlin-web-site-ja/docs/reference/extensions.html

Discussion