【Android & Kotlin】Factory Methodを使って実現方法が異なる目的が同じ機能をスマートに実装してみた

Android & Kotlin

実現方法が異なる目的が同じ機能というのは、例えばマインスイーパーを開発していてタイル(マス)の種類を将来的に四角だけでなく三角などを増やしたいというときに、タイルを開くという機能の目的は同じでも実現方法が異なってきます。

そそたた
そそたた

通信処理をBluetooth、WiFiの両方で実装するケースも該当します。

無計画に実装するとソースコードのあちこちでif文を使って三角、四角の場合というように分岐する残念なスパゲッティコードになってしまいます。

このようなケースは、デザインパターンFactory Methodを使うとスマートに実装が可能です。

そそたた
そそたた

本記事では、companionを使ってFactory Methodを実装します。

Factory Methodにも色んな実装方法がありますので概念、目的などの詳細に関しては、Google先生に聞いてください。

実装例からどのようにスマートなのかを説明することに着目します。

Kotlinでの実装例として、フラグメント(GameFragment)でタッチイベントを受信したときに四角タイル制御クラス(Square)、三角タイル制御クラス(Triangle)を呼び出すケースを説明します。

実装例の比較

まずは、if文で分岐する例を記載します。

var square: Square = Square()
var triangle: Triangle = Triangle()

override fun onTouch(e: MotionEvent): Boolean {
    if (type = TileType.Square) {
        square.action(e.x, e.y);
    } else if (type = TileType.Triangle) {
        triangle.action(e.x, e.y);
    }
}

五角、六角と増えていくと同じ数だけif文の分岐も増えていきます。

そそたた
そそたた

このようなソースコードは、バグ修正や機能追加などでソースコードを改造するときに、改造漏れが起きやすくバグの温床になりがちです。

次に、Factory Methodを使った例を記載します。

var tile: TileController? = null
val type: TileType = TileType.Square もしくわ TileType.Triangle
tile = TileController.create(type)

override fun onTouch(e: MotionEvent): Boolean {
    tile?.action(e.x, e.y);
}

ポイントは、Square、Triangleのオブジェクトを直接呼び出すのではなく、共通インターフェースであるTileControllerを呼び出している点です。

五角、六角と増えてもtile?.action(e.x, e.y)の部分が変更不要でありスマートです。

ソースコード

開発環境は次の通りです。

PCMacBook Pro(2016年モデル)
IDEAndroid Studio 4.0.1
Android SDKminSdkVersion 21
targetSdkVersion 30
言語Kotlin 1.3.72
// TileType.kt
package com.sosotata.minesweeper.model

enum class TileType {
    Square,
    Triangle
}
// TileController.kt
package com.sosotata.minesweeper.model

/**
 * タイル制御インターフェース
 */
interface TileController {
    companion object {
        fun create(type: TileType): TileController = when (type) {
            TileType.Square -> Square()
            TileType.Triangle -> Triangle()
        }
    }

    fun action(x:Float, y:Float): Boolean
}
// Square.kt
package com.sosotata.minesweeper.model

/**
 * 四角タイル制御
 */
class Square : TileController {
    override fun action(x: Float, y: Float): Boolean {
        println("SquareTileController.action()")
        return true
    }
}
// Triangle.kt
package com.sosotata.minesweeper.model

/**
 * 三角タイル制御
 */
class Triangle: TileController {
    override fun action(x: Float, y: Float): Boolean {
        println("TriangleTileController.action()")
        return true
    }
}
// GameFragment.kt
package com.sosotata.minesweeper.ui.main

import android.os.Bundle
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.sosotata.minesweeper.R
import com.sosotata.minesweeper.model.TileController
import com.sosotata.minesweeper.model.TileType
import com.sosotata.minesweeper.model.Triangle
import kotlinx.android.synthetic.main.game_fragment.*

class GameFragment : Fragment(), View.OnTouchListener {
    companion object {
        fun newInstance() = GameFragment()
    }

    private var tile: TileController? = null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View {
        return inflater.inflate(R.layout.game_fragment, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        squareView.setOnTouchListener(this)
        tile = TileController.create(TileType.Square)
    }

    override fun onTouch(v: View, e: MotionEvent): Boolean {
        tile?.action(e.x, e.y)
        return true
    }
}

コメント

タイトルとURLをコピーしました