Should I further break the ScientificCalculator into TrigonometricCalculator, LogarithmicCalculator, etc.?
That's not meaningfully answerable without being explicitly defined by the actual software spec. The exact definition of each type of calculator is not a universal truth; it's a matter of convention and naming - for which you need to have a clear spec.
Secondly, it's unclear why you need to separate these into different calculators to begin with. If there is such a need, it'd be described in the software spec. If never asked for, then there's no reason to arbitrarily subdivide and bundle the mathematical operations you're trying to support.
Inheritance
It's perfectly reasonable to let a scientific calculater inherit from a simple calculator, IF there is an expectation that a scientific calculator can also be used like a simple calculator.
In short, does a user need to be able to ask a scientific calculator what 1+1
equals? If yes, use inheritance. If no, don't.
I need to create another class ScientificCalculator which needs above methods plus other methods like Sin, tan, cos, log, etc.
Based on this statement, it appears the answer to that question is "yes".
public class ScientificCalculator : SimpleCalculator
{
}
Composition
If the scientific calculator is not intended to be used like a simple calculator, but it does internally rely on simple calculations to do its scientific calculations, then composition is the better approach. Essentially, instead of making the scientific calculator a simple calculator in and of itself, you give it one.
public class ScientificCalculator
{
private readonly SimpleCalculator _simpleCalculator;
public ScientificCalculator(SimpleCalculator simpleCalculator)
{
_simpleCalculator = simpleCalculator;
}
}
Interfaces
If the scientific calculator needs to support the same basic operations, but the result is supposed to be different (e.g. a DegreesCalculator
for which 355 + 10 = 5
), then you should be looking at interfaces.
This allows you to retain the same basic operations, but vary the specific implementations
public interface ISimpleCalculator
{
public int Add(int a, int b);
public int Subtract(int a, int b);
// ...
}
public class SimpleCalculator : ISimpleCalculator
{
public int Add(int a, int b)
{
return a + b;
}
// ...
}
public class DegreesCalculator : ISimpleCalculator
{
public int Add(int a, int b)
{
return (a + b) % 360;
}
// ...
}
It's tempting to instead use inheritance and simply override the behavior, but that's inheritance abuse. It might not matter for a small codebase but this distinction is very important for more abstract and complex codebases.
Interface segregation
In certain circumstances, it may be better to forgo the concept of bundled operations altogether and instead make distinct classes (and interfaces) for distinct mathematical operations.
In the above example, you'd have separate IAdder
, ISubtracter
, IMultiplier
, ... interfaces instead of the combined ISimpleCalculator
interface.
For the current trivial example scenario, that's overkill. But for a more abstracted and complex domain with custom operations, it can be a much needed improvement to not arbitrarily bundle some things and not others.