安卓计算器应用的最佳实践是什么?

3
大家好,我是Android世界的新手,正在尝试构建一个中等难度的计算器应用程序。我想出了这样一种结构,但还没有实现任何东西,因为我有点怀疑这是否是正确的方法。所以我来了:
我有一个“Operand”接口,其中包含一个名为“getValue()”的方法,该方法应返回double类型,以及一些其他实现该接口的对象,从而实现了“getValue()”方法:
  • Expression, I thought about making this an object with a private member field which would store all the Operations, so this Expression Object would have something like this:

    public class Expression implements Operand {
    
        private List<Operation> operationList;
        ... 
    }
    

    The operationList would be an ArrayList where Operation is another object that would involve two operands (thus two Operand interfaces as type) bound together with an Operator object which has an enum type that says if the operation which involves this two operands is a SUM, DIV, MUL or a SUB. Something like:

    public class Operation {
    
        private Operand operand1;
        private Operand operand2;
        private Operator operator;          
    
        public Operation(Operand operand1, Operator operator, Operand operand2) {
             this.operand1 = operand1;
             this.operand2 = operand2;
             this.operator = operator;
        }
    
        public double getResult() {
             if (operator.getType() == Operator.Type.SUM) {
                 return operand1.getValue() + operand2.getValue();
             }
             else if (operator.getType() == Operator.Type.SUB) {
                 return operand1.getValue() - operand2.getValue();
             }
             else if (operator.getType() == Operator.Type.MUL) {
                 return operand1.getValue() * operand2.getValue();
             }
             else if (operator.getType() == Operator.Type.DIV) {
                 return operand1.getValue() / operand2.getValue();
             }
             return 0;
        }
        ... 
    }
    
通过接口,即使在像“3 +(4 * 3-2 *(4-1)/ 2 + 5)”这样的表达式内部有一个子表达式,它也将被视为操作数进行评估,特别是在这种情况下。
 operator   All this is another Expression object (a sub-expression that is treated 
   |         |                                     like an Operand
   | ________|____________                         cause it implements the Operand
 3 + (4 * 3 - 2 * (4 - 1))                         interface).
 |
Operand operand1
  • Number类实现了Operand接口,它将是一个简单的包装类,包含一个double值,并在它实现的getValue()方法中返回该double值。

  • Function类有一个枚举类型,指示它是SIN、COS、TAN、ARCSIN、ARCCOS、ARCTAN、LOG还是LN函数,始终使用getValue()方法返回函数计算的double结果;

  • Utility类可能会被误解:它是一个用于指数、平方根、数字百分比或其阶乘等运算的类。这里的想法是再次使用枚举类型来区分操作类型。我知道指数是一种独立的运算,但我认为将其与先前解释的Operation类稍微分开会更好,因为结构和计算方式不同(我希望像操作数类型一样将指数作为操作数类型而不是操作符类型进行处理,对我来说,操作只涉及两个操作数和一个操作符)。

然后我知道,如果给定一个表达式:

3 + 4 * ((5 + 2) - √4 + sin(4) + 3²) / 2

数据结构将是:
  Operand                              Operand
    |                                  |   
3 + 4 * ((5 + 2) - √4 + sin(4) + 3²) / 2
|       ¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯  
Operand             Operand

4个操作数,因此有3个操作,但我需要确定优先级,所以我认为应该迭代操作列表(Expression对象中的私有字段)以获得这样的优先级,因为我的数据结构将会是这样的(基于这个表达式的伪代码):
列表:
- Item n° 1 -> Operation(Number(3), Operator(Operator.Type.SUM), Number(4));
- Item n° 2 -> Operation(Number(4), Operator(Operator.Type.MUL), Expression("(5 + 2) - √4 + sin(4) + 3²"));
- Item n° 3 -> Operation(Expression("(5 + 2) - √4 + sin(4) + 3²"), Operator(Operator.Type.DIV), Number(2));

遍历这个列表后,我发现操作编号2(第2项)必须在操作编号1之前执行。但我认为这种方式不太好,因为每次从操作获取结果时,我都需要整理整个ArrayList。当执行操作N° 2并获得双倍结果时,我需要为该结果创建一个Number()对象,以再次将其视为操作数,并重新组织ArrayList,使操作1的第二个操作数不再是Number(4),而是操作2的getResult()中包装的新结果。另外,位置3的操作不再具有表达式作为第一个操作数,而是具有先前操作的结果作为操作数。
对我来说,这种结构有点难以处理,我想知道是否有人遇到过同样的问题并提出了更好的解决方案,或者这个解决方案可以接受。另一种方法是将完整的表达式存储为字符串,并使用RegExp解析它以确定所有操作的顺序。如果让用户随时更改操作数,例如在输入表达式时,这是更好的解决方案。
3 + 4 * ((5 + 2) - √4 + sin(4) + 3²) / 2

他可以在点击“等于”按钮执行操作之前将√4更改为ln(6)。因此,我猜使用先前的ArrayList来管理这种更改可能会很困难,因为我需要保持表达式中每个操作数的位置...
如何在计算器应用程序中实现这种功能,例如谷歌的计算器应用程序如何做到这一点?例如,如果您查看 ->https://www.google.it/search?q=2%2B2&oq=2%2B2&aqs=chrome.0.69i59j0l2j69i65l2j0.1141j0j7&sourceid=chrome&es_sm=94&ie=UTF-8#q=+3+%2B+4+*+((5+%2B+2)+-+sin(4)+%2B+3)+%2F+2。我知道这是Javascript,但我想逻辑对于每种语言都是相同的。
你认为可能的解决方案是什么?感谢关注!
编辑:有些我没有理解的东西:
  /**
       Evaluates a simple expression (such as "1+1") and returns its value.
       @throws SyntaxException in these cases:
       <ul>
       <li> the expression is not well-formed
       <li> the expression is a definition (such as "a=1+1")
       <li> the expression is an implicit function (such as "x+1")
       </ul>
     */
    public synchronized double eval(String expression) throws SyntaxException {
        return compiler.compileSimple(this, expression).eval();
    }

这个方法调用编译器编译对象的.compileSimple方法:
Function compileSimple(Symbols symbols, String expression) throws SyntaxException {
    rpn.setConsumer(simpleCodeGen.setSymbols(symbols));
    lexer.scan(expression, rpn);
    return simpleCodeGen.getFun();
}

这句话的意思是:返回一个函数对象,然后调用eval()方法。查看Function.eval()方法,可以看到以下内容:
/**
       Evaluates an arity-0 function (a function with no arguments).
       @return the value of the function
    */
    public double eval() {
        throw new ArityException(0);
    }

方法eval必须返回一个double类型,实现会抛出一个ArityException异常,其实现如下:
public class ArityException extends RuntimeException {
    public ArityException(String mes) {
        super(mes);
    }

    public ArityException(int nArgs) {
        this("Didn't expect " + nArgs + " arguments");
    }
}

它如何评估字符串并在抛出ArityException时返回double?
1个回答

2
您可能想要查看随平台打包的官方Android计算器应用程序的源代码。
我认为Logic.java将是您要寻找的类。它具有格式化、检查运算符、估值等代码:Android Calculator 编辑 Android计算器使用Arity Arithmetic Engine,这是一个用于评估表示为字符串的算术表达式的开源库。我无法找到该项目的活动链接,它已从code.google.com中删除。但您可以参考以下链接了解更多信息:
  1. http://www.developerfusion.com/project/62854/arity/
  2. 下载arity .jar

谢谢提供的链接,我看到它使用了一个叫做arity的JAR库,它接收一个表达式字符串并像操作一样对其进行求值。因此,我可以假设当用户最终按下等号按钮时,我可能应该实现一种.eval()函数来评估完整的表达式字符串? - tonix
是的,它使用arity引擎。实际上,许多计算器应用程序都使用该库。我为了提供更多信息而编辑了这个答案。 - Basant Singh
谢谢分享,我看了一下arity代码,有一件事我不明白:它是如何对带有操作符的字符串进行求值的?如果你查看Symbols对象的eval()方法,你会发现它调用了Compiler成员变量的compiler.compileSimple(this, expression)方法,该方法又返回一个Function对象,然后在该对象上调用了eval()方法,但是该方法的实现会抛出一个ArityException类型的异常,并且不会返回一个double类型的值...请检查我的编辑。 - tonix

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接