UML Class Diagrams – Best Design for Calculator Project

cjavaobject-orienteduml

I'm a novice programmer i draw two class diagrams for calculator Project, i want you to check each one of them and tell me which one is better and also, i would appreciate it if you point out the parts that are wrong or could be better.

enter image description here

enter image description here

Best Answer

Lets start from the top. We want a Calculator that can take an input string and turn it into an Abstract Syntax Tree of expressions. I won't go into any specifics on lexing/parsing, but I can conclude that the Composite pattern is a good fit for this.

For the Composite pattern we start by defining an Expression interface that is a bit more general than yours;

interface Expression {
    double evaluate();
}

The idea of the Composite pattern is that any Expression can hold more Expression objects. This allows the lexer/parser to build an AST of expressions with a single root expression. Example:

// Don't mind the shorthand notation for brevity
Expression expr = constructAst("4 + 2 * 5") // AddExpr(4, MultExpr(2, 5));

// Evalutes 2 * 5 and then 4 + that result
expr.evaluate(); 

Now to allow for nesting of multiple expressions, the BinaryExpression needs to hold two expressions, instead of operating on two doubles:

abstract class BinaryExpression implements Expression {
    private Expression left;
    private Expression right;

    public BinaryExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    public double evaluate() {
        evaluate(left.evaluate(), right.evaluate());
    }

    abstract protected double evaluate(double left, double right);
}

This approach does however require you to define expressions for numbers:

class NumberExpression implements Expression {
    private double number;

    public NumberExpression(double number) {
        this.number = number;
    }

    public double evaluate() {
        return (double) number;
    }
}

In your parser, you can cast integers to double to reuse the NumberExpression.

You can then easily define new binary expressions like so:

class AdditionExpression extends BinaryExpression {
    protected double evaluate(double left, double right) {
        return left + right;
    }
}

However, you can now also define a number of other expressions as you see fit:

class LogarithmExpression implements Expression [
    private Expression operand;

    public LogarithmExpression(Expression operand) {
        this.operand = operand;
    }

    public double evaluate() {
        return Math.log(operand.evaluate());
    }
}

class NegateExpression implements Expression {
    private Expression operand;

    public NegateExpression(Expression operand) {
        this.operand = operand;
    }

    public double evaluate() {
        return -operand.evaluate();
    }
}

Skipping the complex implementation details of the lexer/parser, your Calculator can now look like this:

class Calculator {
    public double calculate(string expression) {
        calculate(parseExpressionToAST(expression));
    }

    public double calculate(Expression expression) {
        return expression.evaluate();
    }
}

double twentyTwo = Calculator::calculate("2 + 4 * 5");

// This should be the same as the above calculation, since
// this is the AST of expressions that should be built by the
// lexer/parser
double twentyTwoAsWell = Calculator.calculate(new AdditionExpression(
    new NumberExpression(2),
    new MultiplicationExpression(
        new NumberExpression(4),
        new NumberExpression(5)
    )
));

Note: Important things like operator precedence can be handled by the lexer/parser.

Related Topic