はじめに
Kotlinのコードをリファクタリングする上で、Extensionsに書く方法と、Utilに書く方法があります。
使い分けは人それぞれですが、私は独自のルールがあるのでまとめました。
Extensionsとは
Kotlinでは以下のように記述することで、オリジナルのメソッドを追加することができます。
この例でいうと、ImageViewというクラスにloadというメソッドが追加されました。
fun ImageView.load(url: String?) {
Glide.with(context).load(url).into(this)
}
実際に組み込むと以下のようになります。
class SampleActivity : AppCompatActivity() {
private lateinit var binding: ActivityAboutBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_sample)
// 省略
binding.image.load(url)
}
}
処理をワンライナーでまとめることができ、読みやすくなります。
Utilとは
こちらはJavaで似たような方法がよく使われています。
KotlinではObjectをつかってUtilを作ります。
object ImageViewUtil {
fun load(imageView: ImageView, url: String?) {
Glide.with(imageView.context).load(url).into(imageView)
}
}
実際に組み込むと以下のようになります。
class SampleActivity : AppCompatActivity() {
private lateinit var binding: ActivityAboutBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_sample)
// 省略
ImageViewUtil.load(binding.image, url)
}
}
Utilだと少し長くなってしまいます。
どういうときに使い分けるか
一見するとUtilよりもExtensionsのほうがきれいに書けるので、Extensionsのほうが良いと思えます。
しかし、明確に使い分けしないとコードが無秩序になってしまうため、以下のような判断基準を用いて使い分けています。
メソッドの責任範囲が明確なとき
例えば以下のようなメソッドであれば、Extensionsを使うべきです。
fun ImageView.load(url: String?) {
Glide.with(context).load(url).into(this)
}
この場合はImageViewのみに対して操作を行っているので、Extensionsを使うのは自然です。
しかし以下のように複数のクラスに対して責任を持つメソッドは不適切です。
fun ImageView.load(picture: Picture, textView: TextView) {
Glide.with(context).load(picture.url).into(this)
textView.text = picture.caption
}
上記の場合、ImageViewとTextViewの両方に責任を持ってしまっているため、Extensionsで定義するのは不自然です。
このような複数クラスに責任をメソッドを定義するときはUtilのほうが自然です。もしくはExtensionsもUtilも使わないほうが良いです。
共通した処理があるか
fun ImageView.load(url: String?) {
if (isValidContextForGlide(context)) {
Glide.with(context).load(url).into(this)
}
}
fun ImageView.clear() {
if (isValidContextForGlide(context)) {
Glide.with(context).clear(this)
}
}
private fun ImageView.isValidContextForGlide(): Boolean {
if (context is Activity) {
val activity = context as Activity
if (activity.isDestroyed || activity.isFinishing) {
return false
}
}
return true
}
上記のExtensionsではContentが有効であるか判定する処理をprivateメソッドで共通化しています。
しかしこのprivateメソッドは以下の理由で不自然です。
- ImageViewに対して処理を行うメソッドではない
- 共通化のために無理やりメソッドに切り出している
- privateのExtensions自体に賛否両論がある
このような理由からUtilにしたほうが自然だと私はおもうので、以下のようにします。
object ImageViewUtil {
fun load(imageView: ImageView, url: String?) {
if (isValidContextForGlide(imageView.context)) {
Glide.with(imageView.context).load(url).into(imageView)
}
}
fun clear(imageView: ImageView) {
if (isValidContextForGlide(imageView.context)) {
Glide.with(imageView.context).clear(imageView)
}
}
private fun isValidContextForGlide(context: Context): Boolean {
if (context is Activity) {
if (context.isDestroyed || context.isFinishing) {
return false
}
}
return true
}
}
Util内に共通化処理を入れることによりうまくまとまったと思います。
このUtilを使うために以下のようなExtensionsを使うのは良いと思います。
fun ImageView.load(url: String?) {
ImageViewUtil.load(this, url)
}
fun ImageView.clear() {
ImageViewUtil.clear(this)
}
書いたときの可読性、汎用性
fun Context.isConnected(): Boolean {
val connectivityManager = applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkInfo: NetworkInfo? = connectivityManager.activeNetworkInfo
return networkInfo != null && networkInfo.isConnectedOrConnecting
}
class SampleActivity : AppCompatActivity() {
private lateinit var binding: ActivityAboutBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (isConnected()) {
// 接続しているとき
} else {
// 接続していないとき
}
}
}
以下のことが重要だと思います
- 他のクラスでも利用しやすい、利便性がある
- 名前がわかりやすい
上記の例ではisConnectedというメソッドを用いて、接続しているかいないかをわかりやすく表記しています。
その他
その他、どこまでを Extensionsに委ねるかの基準をきめておくと良いと思います。
例)ドメイン層の処理まではExtensionsで書いても良い など
まとめ
Kotlinを使う上でExtensionsは強力な機能ですが、どのように使い分けるかは考えたほうが良いと思います。
上記の例以外にもいろいろな使い分けルールがこの世の中には存在すると思うので、私も調べていこうと思います。