双人结对,四则运算(三阶段)

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());
    }
}
原文地址:https://www.cnblogs.com/yangqqq/p/13070908.html