12

Hopefully not too academic...

Let's say I need real and complex numbers in my SW library.

Based on is-a (or here) relationship, real number is a complex number, where b in imaginary part of complex number is simply 0.

On the other hand, my implementation would be, that child extends parent, so in parent RealNumber I'd have real part and child ComplexNumber would add imaginary art.

Also there is an opinion, that inheritance is evil.

I recall like yesterday, when I was learning OOP at university, my professor said, this is not a good example of inheritance as absolute value of those two is calculated differently (but for that we have method overloading/polymorfism, right?)...

My experience is, that we often use inheritance to solve DRY, as a result we have often artificial abstract classes in hierarchy (we often have problem to find names for as they do not represent objects from a real world).

Betlista
  • 349

5 Answers5

18

Even if in a mathematical sense, a real number is a complex number, it is not a good idea to derive real from complex. It violates the Liskov Substitution Principle saying (among other things) that a derived class should not hide properties of a base class.

In this case a real number would have to hide the imaginary part of the complex number. It is clear that it makes no sense to store a hidden floating point number (imaginary part) if you only need the real part.

This is basically the same issue as the rectangle/square example mentioned in a comment.

Frank Puffer
  • 6,459
3

not a good example of inheritance as absolute value of those two is calculated differently

This isn't actually a compelling reason against all inheritance here, just the proposed class RealNumber <-> class ComplexNumber model.

You might reasonably define an interface Number, which both RealNumber and ComplexNumber would implement.

That might look like

interface Number
{
    Number Add(Number rhs);
    Number Subtract(Number rhs);
    // ... etc
}

But then you'd want to constrain the other Number parameters in these operations to be the same derived type as this, which you can get close to with

interface Number<T>
{
    Number<T> Add(Number<T> rhs);
    Number<T> Subtract(Number<T> rhs);
    // ... etc
}

Or instead you'd use a language that allowed structural polymorphism, instead of subtype polymorphism. For the specific case of numbers, you might only need the ability to overload arithmetic operators.

complex operator + (complex lhs, complex rhs);
complex operator - (complex lhs, complex rhs);
// ... etc

Number frobnicate<Number>(List<Number> foos, Number bar); // uses arithmetic operations
Caleth
  • 12,190
0

Solution: Do not have a public RealNumber class

I would find it totally OK if ComplexNumber had a static factory method fromDouble(double) that would return a complex number with imaginary being zero. You can then use all the operations that you would use on a RealNumber instance on this ComplexNumber instance.

But I have trouble seeing why you would want/need to have a public inherited RealNumber class. Usually inheritance is used for these reasons (out of my head, correct me if missed some)

  • extending the behaviour. RealNumbers cannot do any extra operations complex number can't do, so no point in doing this.

  • implementing abstract behaviour with a specific implementation. Since ComplexNumber should not be abstract this also does not apply.

  • code reuse. If you just use the ComplexNumber class you reuse 100% of the code.

  • more specific/efficient/accurate implementation for a specific task. This could be applied here, RealNumbers could implement some functionalities faster. But then this subclass should be hidden behind the static fromDouble(double) and should not be known outside. This way it would not need to hide the imaginary part. For the outside there should only be complex numbers (which real numbers are). You could also return this private RealNumber class from any operations in the complex number class that results in a real number. (This assumes the classes are immutable as most number classes.)

It is like implementing a subclass of Integer that is called Zero and hardcode some of the operations since they are trivial for zero. You could do this, as every zero is an integer, but don't make it public, hide it behind a factory method.

findusl
  • 142
0

Saying that a real number is a complex number has more meaning in mathematics, especially set theory, than computer science.
In mathematics we say :

  • A real number is a complex number because the set of complex numbers includes the set of real numbers.
  • A rational number is a real number because the set of real numbers includes the set of rational numbers (and the set of irrational numbers).
  • An integer is a rational number because the set of rational numbers includes the set of integers.

However, this does not mean you must, or even should, use inheritance when designing your library to include a RealNumber and ComplexNumber class. In Effective Java, Second Edition by Joshua Bloch; Item 16 is "Favor Composition Over Inheritance". To avoid the problems mentioned in that item, once you have your RealNumber class defined, it can be used in your ComplexNumber class:

public class ComplexNumber {
    private RealNumber realPart;
    private RealNumber imaginaryPart;

    // Implementation details are for you to write
}

This allows you all the power of reusing your RealNumber class to keep your code DRY while avoiding the issues identified by Joshua Bloch.

0

There are two issues here. The first is that it's common to use the same terms for the types of containers and the types of their contents, especially with primitive types like numbers. The term double, for example, is used to describe both a double-precision floating-point value and a container in which one may be stored.

The second issue is that while is-a relationships among containers from which various types of objects can be read behave the same as the relationships among the objects themselves, those among containers into which various types of objects can be placed behave opposite those among their contents. Every cage that's known to hold an instance of Cat will be a cage that holds an instance of Animal, but need not be be a cage that holds an instance of SiameseCat. On the other hand, every cage that can hold all instances of Cat will be a cage that can hold all instances of SiameseCat, but need not be a cage that can hold all instances of Animal. The only kind of cage that can hold all instances of Cat and can be guaranteed never hold anything other than an instance of Cat, is a cage of Cat. Any other kind of cage would either be incapable of accepting some instances of Cat that it should accept, or would be capable of accepting things that are not instances of Cat.

supercat
  • 8,629