The title says it all. I’m trying to build a calculator application (for self-learning purposes). The application is going to have a very common UI, with plus(+), minus(-), multiply(*) and a divide(/) button. Also, the app can do, real-number as well as complex-number calculations.
So, the situation here is, depending upon the mode(normal or complex), the same button should perform different calculation ? Which pattern should I be using for this situation ? I feel, strategy pattern should be a good fit, but then, I don’t exactly know how to implement that – I mean, I’m just not sure about how to design my classes, what to have as Interfaces, and what as delegates.
CURRENTLY, MY DESIGN CONTAINS
IOperation
{
Do();
}
Add:IOperation{}
Subtract:IOperation{}
Multiply:IOperation{}
Divide:IOperation{}
Root:IOperation{} //not supported by ComplexNumber
ISupportedOperation
{
IList<IOperation> SupportedOps {get;}
}
INumber : ISupportedOperation {}
RealNumber:INumber{}
ComplexNumber : INumber {}
- Interface names starts with I
- All others are concrete classes
BUT IT’S A MESS. And, I’m totally lost within my own classes, and interfaces.
PS: Of course, I can do this using if/else, but that’s not what I want to do. Not because I forcefully want to use a pattern, but because, those if-else will be scattered everywhere in the program for eg. ReadInput, PlusButtonClick, MinusButtonClick, etc. And, as I understand, design patterns are supposed to avoid these kind of situations of code-repetitions, by clever/tricky re-organization of the existing code.
PPS: Sorry for being too verbose.
6
You’re doing this backwards. Forget about the design patterns for awhile.
Just build your calculator program. Build it from scratch, and just design it however it is intuitive to you. Think about it for a few minutes, come up with a few ideas, then roll with it, start to finish.
While you are trying to implement your calculator, you might not design it right the first time and will have to restructure it now and then as you learn more about what’s involved in your design. This is the process of learning, and this will teach you about how you’ve structured your code to attack the problem at hand.
Then, after you’ve done that, then take a look at the design patterns described in the
GoF book, and see what patterns most closely describe how you ended up designing your calculator. Now you know what those patterns are used for, and now you’ll know how to choose what pattern makes sense to apply when.
Design patterns are not building blocks, they are names for common structural paradigms, to improve communication with other developers.
6
First of all, a simplification: all real numbers are a subset of complex numbers, so just do everything with complex numbers.
Secondly, why isn’t Root
supported by ComplexNumber
? If you’re supporting complex numbers, then you can take the root of any number, including negative or complex ones. It all just becomes vector multiplication. So if it’s all complex numbers, then you don’t need to have ISupportedOperation
, since every operation is supported (short of divide by zero, of course, but we usually handle that by throwing an exception).
Really, this should do:
interface ICalculator
{
IComplexNumber Add(IComplexNumber a, IComplexNumber b);
IComplexNumber Subtract(IComplexNumber a, IComplexNumber b);
IComplexNumber Multiply(IComplexNumber a, IComplexNumber b);
IComplexNumber Divide(IComplexNumber a, IComplexNumber b);
IComplexNumber Root(IComplexNumber a, IComplexNumber b);
}
Remember, too, that a square root is just a^(1/2)
, so a more generic interface would be:
interface ICalculator
{
IComplexNumber Add(IComplexNumber a, IComplexNumber b);
IComplexNumber Subtract(IComplexNumber a, IComplexNumber b);
IComplexNumber Multiply(IComplexNumber a, IComplexNumber b);
IComplexNumber Divide(IComplexNumber a, IComplexNumber b);
IComplexNumber Power(IComplexNumber a, IComplexNumber b);
}
2
I’ll take a different approach actually. I’ll suggest you use a strategy pattern (pseudo code – been a while since I used C#).
class CalculatorContext {
private List<Number> numberHistory;
private Number currentDisplay;
private List<Operation> commandHistory;
private Operation currentCommand;
public void evaluateAnswer(Operation op) {
currentCommand = op;
commandHistory.put(op);
Number returnVal = currentCommand.execute(this);
numberHistory.put(currentDisplay);
currentDisplay = returnVal;
}
//Getters/setters.
}
interface Operation {
public Number execute(CalculatorContext ctx);
}
class AddOperation : Operation {
public Number execute(CalculatorContext ctx) {
//Current number to add
Number a = ctx.getCurrentDisplay();
//Previous number to add
assert(ctx.getNumberHistory() != null && ctx.getNumberHistory().size() > 1);
Number b = ctx.getNumberHistory().last();
return a + b;
}
}
The reason I like that approach is you could modify it to handle parenthesis pretty easy but also it reflects how a real calculator works (a current state then modified via operations). In the method where you don’t use a queue-like approach you then need to manage that in a different and much more painful way from the UI. Things like square root become a problem, whereas here you only get “A”, not A + B. You could use a ‘power’ like above but the problem with that is – presumably – you don’t want the CalculatorContext or the UI to be aware of the specifics of that operation. So I’d probably handle it in the operation and if it became burdensome collecting the parameters I’d build helper methods for it.
1
How about this…
interface IOperator
{
double Calculate(double num1, double num2);
// ComplexNumber Calculate(ComplexNumber num1, ComplexNumber num2); // up to you
}
class AddOperator : IOperator
{
public AddOperator() { }
public double Calculate(double num1, double num2)
{
return num1 + num2;
}
// ComplexNumber Calculate(ComplexNumber num1, ComplexNumber num2) { ... }
}
class MinusOperator : IOperator
{
public MinusOperator() { }
public double Calculate(double num1, double num2)
{
return num1 - num2;
}
// ComplexNumber Calculate(ComplexNumber num1, ComplexNumber num2) { ... }
}
// ...
Factory
use to create instance:
static class Factory
{
public static T CreateInstance<T>() where T : new()
{
return new T();
}
}
Client:
static void Main(string[] args)
{
IOperator op = Factory.CreateInstance<AddOperator>();
Console.WriteLine(op.Calculate(1, 2));
// Console.WriteLine(op.Calculate(complexNum1, complexNum2));
Console.Read();
}
Now, if I want to add an operator, just add a suboperator class and implement the IOperator
. Overload method will determine calculate simple or complex.
This is a update version base on Facotry Method use Generic Type.
2
I would start with factory and command pattern like this:
class Program
{
static void Main(string[] args)
{
CalculatorFactory calculatorFactory = new CalculatorFactory();
var arithmeticCalculator = calculatorFactory.GetCalculator<ArithmeticCalculator>();
IOperation addOperation = new AddOperation(arithmeticCalculator, new List<double> { 2, 3 });
IOperation subtractOperation = new SubtractOperation(arithmeticCalculator, new List<double> { addOperation.Execute(), 3 });
Console.WriteLine(subtractOperation.Execute());
var trigonometricCalculator = calculatorFactory.GetCalculator<TrigonometricCalculator>();
IOperation sinOperation = new SinOperation(trigonometricCalculator, 90);
Console.WriteLine(sinOperation.Execute());
Console.ReadLine();
}
}
class AddOperation: IOperation
{
private ArithmeticCalculator arithmeticCalculator;
public List<double> Operands { get; set; } = new List<double>(2);
public AddOperation(ArithmeticCalculator arithmeticCalculator, List<double> operands)
{
this.arithmeticCalculator = arithmeticCalculator;
Operands = operands;
}
public double Execute()
{
return arithmeticCalculator.Add(Operands[0],Operands[1]);
}
}
internal interface IOperation
{
List<double> Operands { get; set; }
double Execute();
}