1.代碼無錯就是優?-簡單工廠模式 (大話設計模式Kotlin版)

面試受挫

面試題:

“用任意一種面嚮對象語言實現一個計算器控制檯程序,要求輸入兩個數和運算符號,得到結果。”

初學者的代碼

以下的程序是利用計算機的方式去思考。看起來並沒有什麼大的問題,也能得到正確的結果,但是這種思維卻使我們的程序只能滿足實現當前的需求,不符合易維護、易擴展、易複用,達不到高質量代碼的需求!

/**
 * @create on 2020/4/23 22:50
 * @description 計算器控制檯程序(兩個數的運算)
 * @author mrdonkey
 */
class Program {
    companion object {
        @JvmStatic
        fun main(vararg args: String) {
            val sc = Scanner(System.`in`)
            println("請輸入數字A")
            val strNumberA = sc.nextLine()
            println("請輸入運算符")
            val operate = sc.nextLine()
            println("請輸入數字B")
            val strNumberB = sc.nextLine()
            val strResult: String = when (operate) {
                "+" -> {
                    strNumberA.toDouble().plus(strNumberB.toDouble()).toString()
                }
                "-" -> {
                    strNumberA.toDouble().minus(strNumberB.toDouble()).toString()
                }
                "*" -> {
                    strNumberA.toDouble().times(strNumberB.toDouble()).toString()
                }
                "/" -> {
                    if (strNumberB != "0")
                        strNumberA.toDouble().div(strNumberB.toDouble()).toString()
                    else
                        "除數不能爲0"
                }
                else -> {
                    "不支持該運算"
                }
            }
            println("結果是:$strResult")
        }
    }
}

測試結果:

請輸入數字A
123
請輸入運算符
*
請輸入數字B
3
結果是:369.0

利用封裝的實現

將業務邏輯(計算操作)與界面邏輯(輸入結果)分開,讓它們 的耦合度降低

  • Operate操作類
/**
 * @create on 2020/4/23 23:11
 * @description 把計算的操作類抽出來
 * @author mrdonkey
 */
class Operate {
    companion object {
        @JvmStatic
        fun getResult(numberA: Double, numberB: Double, operate: String): Double {
            return when (operate) {
                "+" -> {
                    numberA.plus(numberB)
                }
                "-" -> {
                    numberA.minus(numberB)
                }
                "*" -> {
                    numberA.times(numberB)
                }
                "/" -> {
                    numberA.div(numberB)
                }
                else -> {
                    0.toDouble()
                }
            }
        }
    }
}
  • 封裝後的程序
/**
 * @create on 2020/4/23 23:09
 * @description 利用封裝來實現 將頁面邏輯與業務邏輯分開
 * @author mrdonkey
 */
class PackageProgram {

    companion object {
        @JvmStatic
        fun main(vararg args: String) {
            try {
                val sc = Scanner(System.`in`)
                println("請輸入數字A")
                val numberA = sc.nextLine().toDouble()
                println("請選擇運算符(+、-、*、/):")
                val operate = sc.nextLine()
                println("請輸入數字B")
                val numberB = sc.nextLine().toDouble()
                println("輸出結果是:${Operate.getResult(numberA, numberB,operate)}")
            } catch (e: Exception) {
                println("你輸入有錯:${e.message}")
            }
        }
    }
}

輸出同上

利用繼承和多態的方式實現鬆耦合

如果要再之上的程序中,添加一種sqrt開根的運算,需要在Operate類的switch加多一個分支。問題是加一個開根運算,卻需要讓加減乘除的運算都得來參與編譯,如果把加法運算不小心改成減法,這將是多糟糕。應該把單獨的操作運算分離出來,提取父類(繼承),修改其中一個一個操作不影響其他幾個,每一個操作都最自己不同的實現(多態)。

  • Operation類
/**
 * @create on 2020/4/23 23:26
 * @description 利用繼承和多態來減少耦合
 * @author mrdonkey
 */
abstract class Operation {
    abstract fun getResult(numberA: Double, numberB: Double): Double
}
  • OperationPlus
/**
 * @create on 2020/4/23 23:28
 * @description TODO
 * @author 加法
 */
class OperationPlus : Operation() {
    override fun getResult(numberA: Double, numberB: Double) = numberA.plus(numberB)
}
  • OperationMinus
/**
 * @create on 2020/4/23 23:28
 * @description TODO
 * @author 加法
 */
/**
 * @create on 2020/4/23 23:29
 * @description 減法
 * @author mrdonkey
 */
class OperationMinus : Operation() {
    override fun getResult(numberA: Double, numberB: Double) = numberA.minus(numberB)
}
  • OperationTimes
/**
 * @create on 2020/4/23 23:30
 * @description 乘法
 * @author mrdonkey
 */
class OperationTimes : Operation() {
    override fun getResult(numberA: Double, numberB: Double) = numberA.times(numberB)
}
  • OperationDiv
/**
 * @create on 2020/4/23 23:30
 * @description 除法
 * @author mrdonkey
 */
class OperationDiv : Operation() {
    override fun getResult(numberA: Double, numberB: Double) = numberA.div(numberB)
}

提取了父類,並且提供一個getResult的抽象方法由具體子類來實現不同的運算操作,現在我們的修個任何一個運算方法,都不會影響其他的運算的代碼了。但是我如何讓計算器知道我希望用哪個運算呢?

簡單工廠模式的應用

用一個單獨的類來決定實例化誰、用哪個運算!這就是簡單工廠的職責。

  • UML類圖結構
    簡單工廠模式UML

  • OperationFactory

/**
 * @create on 2020/4/23 23:33
 * @description 操作的工廠,讓它知道該實例化哪個操作
 * @author mrdonkey
 */
class OperationFactory {
    companion object {
        @JvmStatic
        fun creatOperate(operate: String): Operation? = when (operate) {
            "+" -> {
                OperationPlus()
            }
            "-" -> {
                OperationMinus()
            }
            "*" -> {
                OperationTimes()
            }
            "/" -> {
                OperationDiv()
            }
            else -> {
                null
            }
        }
    }
}
  • Client測試
/**
 * @create on 2020/4/23 23:37
 * @description 客戶端測試
 * @author mrdonkey
 */
class Client {
    companion object {
        @JvmStatic
        fun main(vararg args: String) {
            try {
                val sc = Scanner(System.`in`)
                println("請輸入數字A")
                val numberA = sc.nextLine().toDouble()
                println("請選擇運算符(+、-、*、/):")
                val operate = sc.nextLine()
                println("請輸入數字B")
                val numberB = sc.nextLine().toDouble()
                val operation = OperationFactory.creatOperate(operate)
                println("輸出結果是:${operation?.getResult(numberA, numberB)}")
            } catch (e: Exception) {
                println("你輸入有錯:${e.message}")
            }
        }
    }
}

輸出同上

這樣子,你只需要輸入相應的運算符號,工廠就實例化對應的運算對象,通過多態返回父類方式實現了計算器的結果。如果以後需要增加各種複雜運算,比如平方根、立方根等,只需要增加對應的子類運算和給工廠添加相應的switch分支即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章