10

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).

  • 7
    this looks like covered in prior question: Should rectangle inherit from square? – gnat Jan 9 at 13:37
  • 1
    @gnat Oh man, that was another example I wanted to use... Thanks! – Betlista Jan 9 at 14:14
  • 7
    ... Note that the sentence "real number is a complex number" in the mathematical sense is only valid for immutable numbers, so if you use immutable objects, you can avoid the LSP violation (same holds also for squares and rectangles, see this SO answer). – Doc Brown Jan 9 at 15:20
  • 5
    ... Note further the absolute value calculation for complex numbers works also for real numbers, so I am not sure what your professor meant. If you implement an "Abs()" method correctly in an immutable complex number and derive a "real" from it, the Abs() method will still deliver correct results. – Doc Brown Jan 9 at 15:23
  • 3
    Possible duplicate of Should rectangle inherit from square? – BobDalgleish Jan 9 at 17:21
17

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.

  • 2
    Today I saw this "Liskow Substitution Principle" several times, I'll have to read more about it, because I do not know that. – Betlista Jan 9 at 14:17
  • 7
    It is perfectly fine to report the imaginary part of a real number as zero, e.g. through a read-only method. But it makes no sense to implement a real as a complex number where the imaginary part is set to zero. This is exactly a case where inheritance is misleading: while interface inheritance would arguably be fine here, implementation inheritance would result in a problematic design. – amon Jan 9 at 14:26
  • 4
    It makes perfect sense to have real numbers inherit from complex numbers, as long as both are immutable. And you don't mind the overhead. – Deduplicator Jan 9 at 19:20
  • @Deduplicator: Interesting point. Immutability resolves lots of issues but I am not fully convinced yet in this case. Have to think about it. – Frank Puffer Jan 9 at 20:21
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
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.

  • I am not surprised to get some downvote, since I have no source to prove. Also if nobody else had an idea, I always suspect there might be some reason for that. But please tell me why you think it is wrong and how you would make it better. – findusl Jan 10 at 9:06
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.

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.