Iniziamo dall'alto. Vogliamo un Calculator
che possa prendere una stringa di input e trasformarla in una Tree Syntax Tree of expressions. Non entrerò in alcun dettaglio su lexing / parsing, ma posso concludere che il pattern Composite è adatto per questo.
Per il pattern Composite, iniziamo definendo un'interfaccia Expression
che è un po 'più generica della tua;
interface Expression {
double evaluate();
}
L'idea del pattern Composite è che qualsiasi Expression
può contenere più oggetti Expression
. Ciò consente al lexer / parser di costruire un AST di espressioni con una singola espressione radice. Esempio:
// 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();
Ora per consentire l'annidamento di più espressioni, BinaryExpression
deve contenere due espressioni, invece di operare su due doppi:
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);
}
Questo approccio richiede tuttavia che tu definisca le espressioni per i numeri:
class NumberExpression implements Expression {
private double number;
public NumberExpression(double number) {
this.number = number;
}
public double evaluate() {
return (double) number;
}
}
Nel parser, puoi eseguire il cast degli interi per raddoppiare per riutilizzare NumberExpression
.
È quindi possibile definire facilmente nuove espressioni binarie in questo modo:
class AdditionExpression extends BinaryExpression {
protected double evaluate(double left, double right) {
return left + right;
}
}
Tuttavia, ora puoi anche definire un numero di altre espressioni come meglio credi:
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();
}
}
Ignora i complessi dettagli di implementazione del lexer / parser, la tua Calcolatrice ora può assomigliare a questa:
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)
)
));
Nota : cose importanti come la precedenza degli operatori possono essere gestite dal lexer / parser.