-1

Consider a SimpleCalculator class which contains four methods (Add, Subtract, Multiply, Divide).

I need to create another class ScientificCalculator which needs above methods plus other methods like Sin, tan, cos, log, etc.

Should I reuse the functionality by inheriting from SimpleCalculator or should I think of some other alternative (such as using some pattern like chain of responsibility). Below is kinda pseudo code which I am thinking of.

if (operation in SimpleOperations) then use SimpleCalculator else ScientificCalculator

Should I further break the ScientificCalculator into TrigonometricCalculator, LogarithmicCalculator, etc.?

11
  • 4
    For such a simple case, I propose that you try the different alternatives. This will give you a unique experience to solve this kind of problems in the future. – Christophe Mar 10 '20 at 14:46
  • @Christophe I have given the simple example just to explain what I am trying to say, it's okay if it is not interesting to you. I am looking for answers who can further think through it or provide some useful link if it's already well thought. – Deepak Mishra Mar 10 '20 at 15:02
  • I understand but as it is stated, your question is way too broad. A lot of design patterns exist to address some aspects of the question. But you can’t expect from us to provide you 300+ pages on the pros and cons of each of them. You need to narrow down your question if you expect some relevant not opinion driven answer. Furthermore request for links is out of scope here. – Christophe Mar 10 '20 at 15:06
  • oh okay, the question (Is inheritance without polymorphism/overriding a right practice?) is too broad. I thought it just a 'yes' or 'no'. I mean, you can just ignore the example which I mentioned, and then answer it. – Deepak Mishra Mar 10 '20 at 15:18
  • 1
    A justification for inheritance is not whether you intent to use all its features. It only comes down to semantics. Does it make sense to say that a scientific calculator is a simple calculator, with extras? I would say no. The character of the calculator does not change, you just add more features to a calculator. SimpleCalculator is not a generalization of ScientificCalculator, nor is ScientificCalculator a special kind of SimpleCalculator. So, you should think is is-a relationships rather than in "stacking stuff". – Martin Maat Mar 10 '20 at 15:51
2

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.

3
  • "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.", this answers my question. I was getting a "something is not right" feeling, when I was designing the ScientificCalculator by inheriting from SimpleCalculator, so asked this question. – Deepak Mishra Mar 10 '20 at 15:28
  • The only concern which I was thinking was that, instead of creating a big "ScientificCalculator" object, why not creating small objects when needed (such as SimpleCalculator when a simple operation is requested for the first time and if a trigonometric operation is requested then only TrigonometricCalculator) for optimization purpose, because optimization/performance is a software spec by default. – Deepak Mishra Mar 10 '20 at 15:52
  • Also, there can be specific dependencies, (assuming that log table is needed for logarithmic calculator), we would be having the log table even if we don't do any logarithmic operation. The other thing which made me to think is open-closed principle. The ScientificCalculator class will remain open, until we complete the whole development. This will hurt the agile based development methodology. So basically it looks like that the inheritance without polymorphism (just re-usability) is not at all needed in most of the cases. Instead of reusing, it's better to call the actual objec. – Deepak Mishra Mar 10 '20 at 16:29
2

There are no absolutes in software design. Some might say that the base class should be Calculator with two children, SimpleCalculator and ScientificCalculator. But that strictly depends on how you want to apportion the methods and properties between the classes.

2

First, there's nothing wrong with extending a class by another class (inheritance) without overriding its existing members.

Second, doing this (extending but not overriding) doesn't preclude polymorphism.  A base class reference could refer to either of the two classes in your example, thus, polymorphism.

Should I further break the ScientificCalculator into TrigonometricCalculator, LogarithmicCalculator, etc.?

We model with purpose.  If the distinction between Simple, Scientific, Trig, Log etc.. calculators somehow serves some purpose for you, then do it — but we can't answer in the general case without reasons how deep to model.

4
  • In any case the base class would call the base call's function only and the result of that operation would also be the same, so no polymorphism. – Deepak Mishra Mar 10 '20 at 16:00
  • @DeepakMishra, I see your point but respectfully disagree. Two different object types that can be referred to by the same reference is sufficient for polymorphism -- from wikipedia: "the use of a single symbol to represent multiple different types" says it for me, as does "when a name denotes instances of many different classes related by some common superclass". Whether or not there is an actual override is an internal implementation detail for the subclass (and the potential is there regardless). – Erik Eidt Mar 10 '20 at 16:28
  • 1
    In that case, I respectfully disagree to Wikipedia, because it doesn't justify the English meaning of polymorphism. Same applies to the wikipedia's definition of encapsulation. – Deepak Mishra Mar 10 '20 at 16:38
  • @DeepakMishra, Yeah, I get it. However, my point of view, first, is that of the external consuming client programmer: those using these classes, and only secondarily that of the implementing programmer. Clients don't -- and shouldn't really -- know the internal implementation of the base and subclass, so to their point of view, there is polymorphism: these clients simply trust that the right base or override method is being used for instances of different classes. If during maintenance, an override is added or removed, the consuming client programmer shouldn't have to care, terminologically. – Erik Eidt Mar 10 '20 at 20:36
1

Basically, what you describe here is implementation inheritance (as opposed to interface inheritance). This is different from the "is-a" relationship which presupposes that client code is going to use the derived class polymorphically through a variable that has the type of the base class (i.e., beyond construction, clients will exclusively use the methods available on the base class). This is essentially the Liskov Substitution Principle - the primary motivation is to make use of the abstraction defined by the public methods on the base class, rather than to reuse the implementation provided by the base (although that's a plus), so that you can substitute derivatives without affecting client code.

This doesn't seem to be the case for what you describe; rather, it looks like the primary motivation is to reuse the implementation defined elsewhere. Implementation inheritance of this sort is not wrong in itself, but it should be limited, and you (and your team) should be aware of the difference (and probably document it somewhere). In some languages (C#, Java) you can't inherit more then one base class (more then one implementation), so that could also pose a problem.
There are other options besides inheritance in different languages, like mixins.

Implementation inheritance can also be seen as a form of hardcoded composition (you "include" a different object that handles some of the methods), so another alternative is to actually design it as a composition by referencing an instance of the "base" class (this can be more flexible, e.g., if you use something like the Strategy Pattern, you can vary the behavior). The downside is that in most languages you have to write some boilerplate to provide the methods that you would otherwise inherit, and forward the calls. Some languages make this sort of thing easier (search JavaScript prototypal inheritance, and JavaScript mixins, or delegation with method_missing in Ruby).

should I think of some other alternative (such as using some pattern like chain of responsibility)

In statically typed languages, Chain of Responsibility is generally speaking an overkill for this kind of thing; this pattern is easier to apply when there's already some sort of chain-like structure in your application - e.g., in UI programming, you'd apply something like that if you already have a tree of UI elements, and you are walking down some path down that tree, and passing some input event down the chain (e.g, a "click" event, where each node down the chain tries to determine if the click happened within its own bounds). For this to work, you need to have some kind of interface for passing those messages. Again, this is somewhat easier to do in an ad hoc fashion in dynamically typed languages. So, unless you already have some such structure, you introduce a lot of complexity for limited gains, so I would recommend against using the pattern.

There are pros and cons to everything, and you have to weigh them out, and perhaps reassess them at some point later in time. It's probably OK to simply inherit what you need at this point. Keep it simple, you can restructure later; it's worse if you introduce a wrong abstraction now, because it will be more complicated and harder to deal with later.

It over time the parts of code related to this don't change much or if the changes don't constantly make you rewrite the clients and the interfaces (cascading changes), then don't worry about it. On the other hand, if you keep finding that your code "resists" change, then take a step back, see what keeps changing and what doesn't, what you can DRY up, reconceptualize, etc., and restructure the code so that those kind of changes are easy to make.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.