I'm also not understanding why the decimal calculator would need 3 numbers, but one possible approach is to have the add()
and multiply()
take an arbitrary amount of numbers and add/multiply them all together.
Javacript example:
class DecimalCalulator extends AbstractCalculator {
add(...numbers: number[]): number {
return numbers.reduce((sum, n) => sum + n);
}
multiply(...numbers: number[]): number {
return numbers.reduce((product, n) => product * n);
}
}
Note that these methods can be static
, so already we start to question why this is a class an what an instance of this class represents.
If the instance has stored the previous number as a property, then add()
and multiply()
should take only one number and be void
methods which modify the stored value.
class AbstractCalculator {
protected total: number = 0;
/*... */
}
class DecimalCalulator extends AbstractCalculator {
add(number: number): void {
this.total += number;
}
multiply(number: number): void {
this.total *= number;
}
}
There are a lot of ways to think about this beyond just classical inheritance. You don't need to use inheritance at all if you don't want to.
Perhaps one of BinaryOperations
and DecimalOperations
is passed into the constructor of a Calculator
and stored as an instance variable this.operations
.
Now the single responsibility of the Calculator
class is to store a rolling total and update it when operations are called. It can have methods like clear()
and undo()
. It doesn't know about the actual math because it delegates that to this.operations.add()
. Note that the Operations
interface is essentially static
here, so there's room to rethink this further.
interface Operations {
add(...numbers: number[]): number;
multiply(...numbers: number[]): number;
}
class Calculator {
private total: number;
private readonly operations: Operations;
constructor( operations: Operations ) {
this.total = 0;
this.operations = operations;
}
clear(): void {
this.total = 0;
}
add( number: number ): void {
this.total = this.operations.add(this.total, number);
}
multiply( number: number ): void {
this.total = this.operations.multiply(this.total, number);
}
}
If this is an interactive calculator, think about what order the actions are called in. You're not just calling an add method with a number, right? You would press the add button first, then enter the number (one digit at a time), and then press the equals button. No math is done until equals is pressed, and the calculator needs to store knowledge of which number and which operation were pressed.
So in that case instead of passing in an Operations
object with methods for each operation, perhaps we pass in an array of Operation
handlers, where an operation has a key
or a symbol
(which we use when storing the last button pressed) and a callback
to call when the time is right.
interface ButtonHandler {
symbol: string;
callback( previousValue: number, enteredValue: number ): number;
}
const DecimalAdd: ButtonHandler = {
symbol: '+',
callback: ( a, b ) => a + b,
} // this is a plain javascript object, not a class, but it implements out interface
I could go on, but that should be enough food for thought.