We talked a bit about thinking about objects. I decided to throw together something to show how even a simple assignment like this can be built with objects in mind. While I was doing this, I fell into old trollish habits. But I just went with it. I figured it kills two birds. I get to show you how to encapsulate the calculator as an object, and everyone else can get some sort of kick out of me turning something simple into something ridiculous
What is a Calculator? It is a set of operations that can be applied to a group of operands. Given an expression, the calculator can evaluate it to produce a result.
Properties: A set of operations
Behaviors: evaluating expression
Code
public interface Calculator {
double evaluate(String expression);
}
A reverse polish notation calculator is a specific implementation of a calculator which evaluates using RPN.
Code
import java.util.LinkedList;
import java.util.Optional;
import java.util.Scanner;
import java.util.Set;
public class ReversePolishNotationCalculator implements Calculator {
private Set<Operation> operations;
public ReversePolishNotationCalculator()
{
//TODO: implement dependency injection framework to decouple
//implementation from specifics of operation creation
this(new BasicOperationSetFactory());
}
public ReversePolishNotationCalculator(OperationSetFactory operationSetFactory)
{
if(operationSetFactory == null)
throw new IllegalArgumentException("operationSetFactory is null");
this.operations = operationSetFactory.createOperationSet();
}
public double evaluate(String expression) {
Scanner parser = new Scanner(expression);
LinkedList<Double> register = new LinkedList<Double>();
while(parser.hasNext())
{
String parsed = parser.next();
Optional<Operation> operation = getOperation(parsed);
if(operation.isPresent())
{
if(register.size() < 2)
throw new IllegalArgumentException("Too many operators");
Double operand1 = register.pop();
Double operand2 = register.pop();
Double result = operation.get().execute(operand1, operand2);
register.push(result);
}
else
{
register.push(Double.parseDouble(parsed));
}
}
if(register.size() != 1)
throw new IllegalArgumentException("Too many operands");
return register.pop();
}
private Optional<Operation> getOperation(String operator)
{
return operations
.stream()
.filter(o -> o.getOperator().equals(operator))
.findFirst();
}
}
Our calculator doesn't care about specifics of its operations. It just cares about applying the rules specified by RPN. For this reason, we can abstract the implementation of Operations behind an interface, and the construction of the Set to a Factory. Fuhhtermore, the factory can be dependency injected into the calculator to further separate concerns and decouple our solution. That would be S, I, and D of SOLID design principles of OOP.