赞
踩
/* * 大致列举一下计算器的功能需求: * 交互式界面,输入算式,按下回车,程序就会帮我们计算出结果; * 数字与字符之间要求有空格,“1 + 1”是可以的,“1+1”则不行; * 输入 exit,按下回车,程序就会退出;支持“加减乘除”,四种运算,仅支持两个数的运算。 */ val help = """ -------------------------------------- 使用说明: 1. 输入 1 + 1,按回车,即可使用计算器; 2. 注意:数字与符号之间要有空格; 3. 想要退出程序,请输入:exit --------------------------------------""".trimIndent() fun main() { while (true) { println(help) val input = readLine() ?: continue if (input == "exit") exitProcess(0) val inputList = input.split(" ") val result = calculate(inputList) if (result == null) { println("输入格式不对") continue } else { println("$input = $result") } } } private fun calculate(inputList: List<String>): Int? { if (inputList.size != 3) return null val left = inputList[0].toInt() val operation = Operation.valueOf(inputList[1]) val right = inputList[2].toInt() return when (operation) { Operation.ADD -> left + right Operation.MINUS -> left - right Operation.MULTI -> left * right Operation.DIVI -> left / right } }
/* 在 2.0 版本中,我们会分成两个阶段: * 第一个阶段,融入面向对象的思想。1.0 版本中,我们只写了两个函数,一个是 main() 函数, 另一个是 calculate() 函数。虽然这样的设计非常直观且便于理解,但却不太符合我们工程界的思维习惯。 我们应该将程序封装到一个类当中,并且尽量让每个函数的功能划分清楚,保持每个函数尽量简单。 * 第二个阶段,兼容输入格式。1.0 版本中,我们对输入有严格的要求,数字和符号之间必须有空格,否则我们的算式解析会出错。 在 2.0 版本中,我们尝试兼容不同的输入格式,不管数字和符号之间有没有空格,我们都要能成功执行。 */ class CalculatorV2 { val exit = "exit" val help = """ -------------------------------------- 使用说明: 1. 输入 1 + 1,按回车,即可使用计算器; 2. 注意:数字与符号之间要有空格; 3. 想要退出程序,请输入:exit --------------------------------------""".trimIndent() fun start() { while (true) { println(help) val input = readLine() ?: continue val result = calculate(input) if (result == null) { println("输入格式不对") continue } else { println("$input = $result") } } } /* 拆分 calculate() 方法主要做了三件事: * 第一,将“是否退出”的逻辑封装到了 shouldExit() 方法当中,如果将来这部分逻辑变得更复杂,我们只改动这一个方法即可。 * 第二,将算式的解析,封装到了 parseExpression() 方法当中,而解析算式的时候也需要解析操作符,这时候我们也需要 parseOperator()。 * 第三,将具体的计算逻辑交给了对应的方法。这么做的原因,是可以让我们的程序变得更加灵活。 比如,我们在下个版本当中会更改“加法”的计算逻辑,那么我们就只需要改动这一个方法就行了。 */ private fun calculate(input: String): String? { if (shouldExit(input)) exitProcess(0) val exp = parseExpression(input) ?: return null val left = exp.left val operator = exp.operator val right = exp.right return when (operator) { Operation.ADD -> addString(left, right) Operation.MINUS -> minusString(left, right) Operation.MULTI -> multiString(left, right) Operation.DIVI -> diviString(left, right) } } private fun addString(left: String, right: String): String { val result = left.toInt() + right.toInt() return result.toString() } private fun minusString(left: String, right: String): String { val result = left.toInt() - right.toInt() return result.toString() } private fun multiString(left: String, right: String): String { val result = left.toInt() * right.toInt() return result.toString() } private fun diviString(left: String, right: String): String { val result = left.toInt() / right.toInt() return result.toString() } private fun shouldExit(input: String): Boolean { return input == exit } private fun parseExpression(input: String): Expression? { val operation = parseOperator(input) ?: return null val list = input.split(operation.value) if (list.size != 2) return null return Expression( left = list[0].trim(), operator = operation, right = list[1].trim() ) } private fun parseOperator(input: String): Operation? { Operation.values().forEach { if (input.contains(it.value)) { return it } } return null } private fun parseOperator1(input: String): Operation? { return when { input.contains(Operation.ADD.value) -> Operation.ADD input.contains(Operation.MINUS.value) -> Operation.MINUS input.contains(Operation.MULTI.value) -> Operation.MULTI input.contains(Operation.DIVI.value) -> Operation.DIVI else -> null } } } enum class Operation(val value: String) { ADD("+"), MINUS("-"), MULTI("*"), DIVI("/") } data class Expression( val left: String, val operator: Operation, val right: String ) fun main() { val calculator = CalculatorV2() calculator.start() } /* 在这个过程中,创建了三个类: * “Calculator”类,代表整个计算器; * “Operation”枚举类,代表加减乘除四种运算操作符; * “Expression”数据类,代表我们算式当中的数字和操作符。 之后,我们又对计算器的核心功能进行了更细颗粒度的拆分, 提高了程序的灵活性,为我们的功能扩展打下了基础。 */
/*
针对 3.0 这个版本,我们也分为了两个阶段:
* 第一阶段,增加单元测试。单元测试是软件工程当中的一个概念,它指的是对软件当中的最小可执行单元进行测试,
以提高软件的稳定性。在 Java 当中,最小单元一般会认为是类,因此,我们一般会以类为单元,对类当中的方法进行一一测试。
* 第二阶段,支持大数的加法。我们知道 Java、Kotlin 当中的整型都是有范围限制的,
如果我们输入两个特别大的数字进行计算,那么程序是无法正常工作的。因此,我们需要对特别大的数进行兼容。
*/
//在 Kotlin 当中,如果要使用单元测试,我们需要在 gradle 文件当中,添加 Kotlin 官方提供的依赖:
//testImplementation 'org.jetbrains.kotlin:kotlin-test'
单元测试的代码,我们一般会放在工程的 test 目录下:
可以从这个图中看出很多信息:
·第一,test 目录、main 目录,它们是平级的目录,内部拥有着相同的结构。main 目录下放的是功能代码,test 目录下放的则是测试代码。
·第二,由于我们要开发 3.0 版本,所以我们在 main 目录下创建了 CalculatorV3 这个类;另外,由于我们需要在 3.0 版本加入单元测试,所以对应的,我们在 test 目录下相同的地方,创建了 TestCalculatorV3。这两个类的关系是一一对应的,CalculatorV3 是为了实现 3.0 版本的功能,TestCalculatorV3 是为了测试 3.0 版本的功能,确保功能正常。
/* 针对 3.0 这个版本,我们也分为了两个阶段: * 第一阶段,增加单元测试。单元测试是软件工程当中的一个概念,它指的是对软件当中的最小可执行单元进行测试, 以提高软件的稳定性。在 Java 当中,最小单元一般会认为是类,因此,我们一般会以类为单元,对类当中的方法进行一一测试。 * 第二阶段,支持大数的加法。我们知道 Java、Kotlin 当中的整型都是有范围限制的, 如果我们输入两个特别大的数字进行计算,那么程序是无法正常工作的。因此,我们需要对特别大的数进行兼容。 */ //在 Kotlin 当中,如果要使用单元测试,我们需要在 gradle 文件当中,添加 Kotlin 官方提供的依赖: //testImplementation 'org.jetbrains.kotlin:kotlin-test' //单元测试的代码,我们一般会放在工程的 test 目录下: class CalculatorV3 { private val exit = "exit" private val help = """ -------------------------------------- 使用说明: 1. 输入 1 + 1,按回车,即可使用计算器; 2. 注意:数字与符号之间要有空格; 3. 想要退出程序,请输入:exit --------------------------------------""".trimIndent() fun start() { while (true) { println(help) val input = readLine() ?: continue val result = calculate(input) if (result == null) { println("输入格式不对") continue } else { println("$input = $result") } } } fun calculate(input: String): String? { if (shouldExit(input)) exitProcess(0) val exp = parseExpression(input) ?: return null val left = exp.left val operator = exp.operator val right = exp.right return when (operator) { Operation.ADD -> addString(left, right) Operation.MINUS -> minusString(left, right) Operation.MULTI -> multiString(left, right) Operation.DIVI -> diviString(left, right) } } /* * 注释①,我们创建了一个 StringBuilder 对象,用于存储最终结果, 由于我们的结果是一位位计算出来的,所以每一位结果都是慢慢拼接上去的, 在这里,为了提高程序的性能,我们选择使用 StringBuilder。 * 注释②,我们定义了两个可变的变量 index,它们分别指向了两个数字的个位,这是因为我们的计算是从个位开始的。 * 注释③,carry,我们用它来存储每一位计算结果的进位。 * 注释④,这个 while 循环当中,我们会让两个 index 从低位一直到高位,直到遍历完它们所有的数字位。 * 注释⑤,这里的逻辑是取每一位上的数字,其中有个细节就是补零操作,比如当程序运行到百位的时候,99 没有百位,这时候 rightVal = 0。 * 注释⑥,当我们的程序计算出结果后,我们要分别算出 carry,以及当前位的结果。这时候我们分别使用“除法”计算 carry,使用“取余”操作计算当前位的结果。 * 注释⑦,这里是为了兼容一个特殊的场景,在“99+1”的情况下,我们的 while 循环最多只会遍历到十位, 如果不做特殊处理的话,结果将变成“99+1=00”。这并不是我们想要的,所以,为了兼容这种特殊情况, 我们在 while 循环结束后增加了一个判断,如果 carry=1,那就说明在最大的那一位数计算完以后,仍然有进位,我们要手动添加。 * 注释⑧,对于一个算式“135+99”,我们的 result 拼接其实是倒叙的“432”,这时候我们需要将其翻转一下,才能得到正确的结果“135+99=234”。 */ private fun addString(leftNum: String, rightNum: String): String { // ① val result = StringBuilder() // ② var leftIndex = leftNum.length - 1 var rightIndex = rightNum.length - 1 // ③ var carry = 0 // ④ while (leftIndex >= 0 || rightIndex >= 0) { // ⑤ val leftVal = if (leftIndex >= 0) leftNum.get(leftIndex).digitToInt() else 0 val rightVal = if (rightIndex >= 0) rightNum.get(rightIndex).digitToInt() else 0 val sum = leftVal + rightVal + carry // ⑥ carry = sum / 10 result.append(sum % 10) leftIndex-- rightIndex-- } // ⑦ if (carry != 0) { result.append(carry) } // ⑧ return result.reverse().toString() } private fun minusString(left: String, right: String): String { val result = left.toInt() - right.toInt() return result.toString() } private fun multiString(left: String, right: String): String { val result = left.toInt() * right.toInt() return result.toString() } private fun diviString(left: String, right: String): String { val result = left.toInt() / right.toInt() return result.toString() } private fun shouldExit(input: String): Boolean { return input == exit } private fun parseExpression(input: String): Expression? { val operation = parseOperator(input) ?: return null val list = input.split(operation.value) if (list.size != 2) return null return Expression( left = list[0].trim(), operator = operation, right = list[1].trim() ) } private fun parseOperator(input: String): Operation? { Operation.values().forEach { if (input.contains(it.value)) { return it } } return null } } fun main() { val calculator = CalculatorV3() calculator.start() }
编写测试代码:
/*
首先,我们定义了一个方法 testCalculate(),
并且使用了一个注解 @Test 来修饰它。因为这样做以后,
IntelliJ 就会知道:哦,这是一个用来做测试的方法。
*/
class TestCalculatorV3 {
@Test
fun testCalculate() {
val calculator = CalculatorV3()
val res1 = calculator.calculate("1+2")
assertEquals("3", res1)
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。