双人结对,四则运算(三阶段)
1、第一阶段目标:重构四则运算-- 能把计算的功能封装起来,通过API 接口调用计算方法。定义一个计算核心类:把四则运算的计算功能包装在一个模块中 (这个模块可以是一个类 Class, 一个DLL等等)。“计算核心”模块和调用类它的其他模块之间是什么关系呢? 它们要通过一定的API (Application Programming Interface) 来和其他模块交流。这个API 接口应该怎么设计呢? 可以从下面的最简单的接口开始:Calc()这个Calc 函数接受字符串的输入(字符串里就是运算式子,例如 “ 5+3.5“, “7/8 – 3/8 ”, “3 + 90 * (-0.3)“ 等等
2 第二阶段目标 - 通过测试程序和API 接口测试其简单的加减乘除功能。并能看到代码覆盖率。可以扩展 Calc() 的定义,让它接受一个新的参数 “precision”, 或者可以启用一个新的函数 Setting()。最多4 个运算符数值范围是 -1000 到 1000精度是小数点后两位怎么通过API 告诉我们的模块呢? 我们当然可以用函数的参数直接传递,但是参数的组合很多,怎么定义好参数的规范呢? 建议大家考虑用 XML 来传递这些参数。增加了新的Setting() 函数之后,要让模块支持这样的参数,同时,还要保证原来的各个测试用例继续正确地工作。
3第三阶段目标 – 定义异常处理。
如果输入是有错误的,例如 “1 ++ 2”, 在数值范围是 -1000 .. 1000 的时候,传进去 “10000 + 32768 * 3”, 或者是 “ 248.04 / 0” 怎么办? 怎么告诉函数的调用者 “你错了”? 把返回的字符串定义为 “-1” 来表示? 那么如果真的计算结果是 “-1” 又怎么处理呢? 建议这个时候,要定义各种异常 (Exception), 让 Core 在碰到各种异常情况的时候,能告诉调用者 - 你错了! 当然,这个时候,同样要进行下面的增量修改:定义要增加什么功能 - 例如:支持 “运算式子格式错误” 异常,写好测试用例,传进去一个错误的式子,期望能捕获这个 异常。 如果没有,那测试就报错。在 Core 模块中实现这个功能,测试这个功能, 同时测试所有以前的功能,保证以前的功能还能继续工作 (没有 regression), 确认功能完成,继续下一个功能。),这个模块的返回值是一个字符串,例如,前面几个例子的结果就是 ( ”17.5“, “ 1/2”, “-24“).
package com.core.bean; import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; import java.util.Stack; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.core.util.JaxbUtil; public class Calculate { private static final Pattern EXPRESSION_PATTERN = Pattern.compile("[0-9\\.+-/*()= ]+"); private static final Map<String,Integer> OP_PRIORITY_MAP = new HashMap<String ,Integer>() { /** * */ private static final long serialVersionUID = -5028404412583819744L; { put("(",0); put("+",2); put("-",2); put("*",3); put("/",3); put(")",7); put("=",20); } }; //不带xml参数的计算 public static String Calc(String expression) throws MyException { //判断表达式是否为空 if(expression==null||expression.equals("")) { throw new IllegalArgumentException("表达式不能为空!"); } Matcher matcher = EXPRESSION_PATTERN.matcher(expression); if(!matcher.matches()){ throw new IllegalArgumentException("表达式含有非法字符!"); } expression = formatExpression(expression); isExpression(expression); Stack<String> optstack = new Stack<>(); Stack<BigDecimal> numStack = new Stack<>(); StringBuilder numBuilder = new StringBuilder(16); char pre; for(int i=0;i<expression.length();i++) { char ch = expression.charAt(i); if(ch!=' ') { pre = ch; if((ch>='0'&&ch<='9')||ch=='.') { numBuilder.append(ch); }else { if(numBuilder.length()>0) { if(isNum(numBuilder.toString())==false) { throw new MyException("表达式格式错误!————数字格式错误(错误数字:'"+numBuilder+"'"); } numStack.push(new BigDecimal(numBuilder.toString())); numBuilder.delete(0, numBuilder.length()); } String operation = String.valueOf(ch); if(optstack.empty()) { optstack.push(operation); }else { if(operation.equals("(")) { optstack.push(operation); }else if(operation.equals(")")) { directCal(optstack, numStack,true); }else if(operation.equals("=")) { directCal(optstack, numStack,false); return numStack.pop().toString(); }else { compareOpt_Cal(numStack, optstack, operation); } } } } } if(numBuilder.length()>0) { numStack.push(new BigDecimal(numBuilder.toString())); } directCal(optstack, numStack, false); return numStack.pop().toString(); } //带xml参数的计算 public static String Calc(String expression,String xml) throws MyException { //判断表达式是否为空 if(expression==null||expression.equals("")) { throw new IllegalArgumentException("表达式不能为空!"); } Matcher matcher = EXPRESSION_PATTERN.matcher(expression); if(!matcher.matches()){ throw new IllegalArgumentException("表达式含有非法字符!"); } expression = formatExpression(expression); isExpression(expression); Option option=JaxbUtil.convertToJavaBean(xml, Option.class); int op_num = 0; Stack<String> optstack = new Stack<>(); Stack<BigDecimal> numStack = new Stack<>(); StringBuilder numBuilder = new StringBuilder(16); for(int i=0;i<expression.length();i++) { char ch = expression.charAt(i); if(ch!=' ') { if((ch>='0'&&ch<='9')||ch=='.') { numBuilder.append(ch); }else { if(++op_num>option.getOperation_num()) throw new MyException("操作符个数超过设置最大个数!"); if(numBuilder.length()>0) { BigDecimal value = new BigDecimal(numBuilder.toString()); if(value.doubleValue()>option.getMax()||value.doubleValue()<option.getMin()) throw new MyException("数据超出设置范围!"); if(isNum(numBuilder.toString())==false) { throw new MyException("表达式格式错误!————数字格式错误(错误数字:'"+numBuilder+"'"); } numStack.push(value); numBuilder.delete(0, numBuilder.length()); } String operation = String.valueOf(ch); if(optstack.empty()) { optstack.push(operation); }else { if(operation.equals("(")) { optstack.push(operation); }else if(operation.equals(")")) { directCal(optstack, numStack,true); }else if(operation.equals("=")) { directCal(optstack, numStack,false); return String.format("%."+option.getPoint_long()+"f", numStack.pop()); }else { compareOpt_Cal(numStack, optstack, operation); } } } } } if(numBuilder.length()>0) { numStack.push(new BigDecimal(numBuilder.toString())); } directCal(optstack, numStack, false); return String.format("%."+option.getPoint_long()+"f", numStack.pop()); } private static void compareOpt_Cal(Stack<BigDecimal> numstack,Stack<String> optstack,String operation) throws MyException { String topOpt = optstack.peek(); int comp_val = getPriority(topOpt, operation); if(comp_val==-1||comp_val==0) { String opt = optstack.pop(); BigDecimal num2 = numstack.pop(); BigDecimal num1 = numstack.pop(); BigDecimal result = calDouble(opt, num1, num2); numstack.push(result); if(optstack.empty()) { optstack.push(operation); }else { compareOpt_Cal(numstack, optstack, operation); } }else { optstack.push(operation); } } //判断两个操作符的优先级 private static int getPriority(String op1,String op2) { return OP_PRIORITY_MAP.get(op2)-OP_PRIORITY_MAP.get(op1); } //两个数之间的运算,注意除数不能为0 private static BigDecimal calDouble(String operation,BigDecimal num1,BigDecimal num2) throws MyException { BigDecimal result = new BigDecimal(0); switch (operation) { case "+": result = num1.add(num2); break; case "-": result = num1.subtract(num2); break; case "*": result = num1.multiply(num2); break; case "/": if(num2.equals(BigDecimal.ZERO)) { throw new MyException("表达式格式错误!————数字格式错误(除数不能为0:'"+num1+"/"+num2+"')"); } result = num1.divide(num2,10,BigDecimal.ROUND_DOWN); break; default: break; } return result; } //递归计算 private static void directCal(Stack<String> opStack,Stack<BigDecimal> numStack,boolean haved) throws MyException { String opt = opStack.pop(); BigDecimal num2 = numStack.pop(); BigDecimal num1 = numStack.pop(); BigDecimal result = calDouble(opt, num1, num2); numStack.push(result); if(haved) { if("(".equals(opStack.peek())) { opStack.pop(); }else { directCal(opStack, numStack,haved); } }else { if(!opStack.empty()) { directCal(opStack, numStack, haved); } } } //判断表达式的合法性(字符合法性) public static void isExpression(String expression) throws MyException { char pre=' ',cur=' '; int l_bracket_num=0,r_bracket_num=0; for(int i=0;i<expression.length();i++) { cur=expression.charAt(i); if(cur=='(') { l_bracket_num++; }else if(cur==')') { r_bracket_num++; } if(i>0) { if(pre=='.'&&(cur=='.'||cur=='('||cur==')'||cur=='+'||cur=='-'||cur=='*'||cur=='/')) { throw new MyException("表达式格式错误!————操作符格式错误(错误字符区域:'"+pre+cur+"')"); }else if((pre=='.'||pre=='('||pre==')'||pre=='+'||pre=='-'||pre=='*'||pre=='/')&&cur=='.') { throw new MyException("表达式格式错误!————操作符格式错误(错误字符区域:'"+pre+cur+"')"); }else if((cur=='.'||cur==')'||cur=='+'||cur=='-'||cur=='*'||cur=='/')&&pre=='(') { throw new MyException("表达式格式错误!————操作符格式错误(错误字符区域:'"+pre+cur+"')"); }else if((pre=='.'||pre=='('||pre=='+'||pre=='-'||pre=='*'||pre=='/')&&cur==')') { throw new MyException("表达式格式错误!————操作符格式错误(错误字符区域:'"+pre+cur+"')"); }else if((pre=='+'||pre=='-'||pre=='*'||pre=='/')&&(cur=='+'||cur=='-'||cur=='*'||cur=='/')) { throw new MyException("表达式格式错误!————操作符格式错误(错误字符区域:'"+pre+cur+"')"); }else if(cur=='='&&i!=expression.length()-1) { throw new MyException("表达式格式错误!————操作符格式错误(错误字符区域:'"+pre+cur+"')"); }else if(cur=='='&&(pre=='+'||pre=='-'||pre=='*'||pre=='/'||pre=='(')) { throw new MyException("表达式格式错误!————操作符格式错误(错误字符区域:'"+pre+cur+"')"); }else if(cur=='('&&(pre>='0'&&pre<='9')) { throw new MyException("表达式格式错误!————操作符格式错误(错误字符区域:'"+pre+cur+"')"); }else if(pre==')'&&(cur>='0'&&cur<='9')) { throw new MyException("表达式格式错误!————操作符格式错误(错误字符区域:'"+pre+cur+"')"); } }else { if(cur=='/'||cur=='*'||cur=='.'||cur==')'||cur=='=') throw new MyException("表达式格式错误!————表达式首字符不符合规范(错误字符:'"+cur+"'"); } pre=cur; } if(l_bracket_num!=r_bracket_num) throw new MyException("表达式格式不正确!————存在不匹配的左右括号"); } //判断表达式中数字的合法性 public static boolean isNum(String num) { if(num.contains(".")) { for(int i=0;i<num.indexOf('.');i++){ if(i==0&&num.charAt(0)=='0'&&num.indexOf('.')>1) { return false; } } }else { if(num.charAt(0)=='0'&&num.length()>1) return false; } return true; } //判断表达式的开头是否为+或-,如果是,则在其前面加上0方便操作 public static String formatExpression(String expression) { String result=""; if(expression.charAt(0)=='+'||expression.charAt(0)=='-') expression = "0"+expression; result = expression.replaceAll("[(]-", "(0-"); result = result.replaceAll("[(][+]", "(0+"); return result; } public static void main(String[] args) throws MyException { Option option = new Option(); //设置最大字符数 option.setOperation_num(10); //设置数值范围的最小值 option.setMin(-1000); //设置数值范围的最大值 option.setMax(1000); //设置结果保留的小数位 option.setPoint_long(2); String xml = JaxbUtil.convertTomXml(option); //传入带xml参数的计算方法(如不需要直接去掉该参数即可) System.out.println(Calc("12+87",xml)); } }
package com.core.bean; public class MyException extends Exception{ /** * */ private static final long serialVersionUID = 4709550237708038191L; public MyException() {} public MyException(String message) {super(message);} }
package com.core.bean; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class Option { private int operation_num; private double min; private double max; private int point_long; public int getOperation_num() { return operation_num; } public void setOperation_num(int operation_num) { this.operation_num = operation_num; } public double getMin() { return min; } public void setMin(double min) { this.min = min; } public double getMax() { return max; } public void setMax(double max) { this.max = max; } public int getPoint_long() { return point_long; } public void setPoint_long(int point_long) { this.point_long = point_long; } }
package com.core.util; import java.io.StringReader; import java.io.StringWriter; import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import com.core.bean.Option; public class JaxbUtil { //javabean转换成xml private static String converToXml(Object obj,String encoding) { String result = null; try { JAXBContext context = JAXBContext.newInstance(obj.getClass()); Marshaller marshaller = context.createMarshaller(); //是否格式化xml(按标签自动换行) marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); //设置编码方式 marshaller.setProperty(Marshaller.JAXB_ENCODING, encoding); StringWriter writer = new StringWriter(); marshaller.marshal(obj, writer); result = writer.toString(); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } return result; } public static String convertTomXml(Object object) { return converToXml(object, "UTF-8"); } //xml转换成javabean @SuppressWarnings("unchecked") public static<T> T convertToJavaBean(String xml,Class<T> c) { T t = null; try { JAXBContext context = JAXBContext.newInstance(c); Unmarshaller unmarshaller = context.createUnmarshaller(); t = (T)unmarshaller.unmarshal(new StringReader(xml)); }catch (Exception e) { // TODO: handle exception e.printStackTrace(); } return t; } public static void main(String[] args) { Option option = new Option(); option.setOperation_num(4); option.setMin(-1000); option.setMax(1000); option.setPoint_long(2); // System.out.println(convertTomXml(option)); String xml = convertTomXml(option); Option option2=convertToJavaBean(xml, Option.class); System.out.println(option2.getMin()+"~"+option2.getMax()); } }