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

mrflash818
  • 163
  • 6
Betlista
  • 349
  • 3
  • 10
  • 7
    this looks like covered in prior question: [Should rectangle inherit from square?](https://softwareengineering.stackexchange.com/questions/338787/should-rectangle-inherit-from-square) – gnat Jan 09 '19 at 13:37
  • It depends entirely on how you intend to use these classes ultimately. Don't get stuck in theory when all you need to do is represent points on a real/imaginary coordinate plane (you'd probably only use complex numbers for this, and for "real" numbers, the imaginary component would be 0). – Neil Jan 09 '19 at 13:50
  • 1
    @gnat Oh man, that was another example I wanted to use... Thanks! – Betlista Jan 09 '19 at 14:14
  • Just because there is an obvious function **R** -> **Z**, doesn't mean that reals are complexes. – Caleth Jan 09 '19 at 14:26
  • Thanks for all answers, links. The biggest confusion came from is-a guideline and my understanding is, that it is just a guideline, not a law... Inheriting real from complex smelled to me too. Based on usage in SW one might want to have this relation or not. Just because we can model it that way it is not an excuse/reason also in regards of DRY and whether it really makes sense is subjective and there is not a correct/incorrect answer in between ComplexNumber extends RealNumber and no inheritance at all... – Betlista Jan 09 '19 at 14:47
  • 1
    In case this question gets closed as a dupe, it should be kept **"On Hold", and not downvoted and deleted**. It would be nice to communicate "complex/real inheritance problem is similar to rectangle/square inheritance problem". – Doc Brown Jan 09 '19 at 15:13
  • 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](https://stackoverflow.com/a/1030573)). – Doc Brown Jan 09 '19 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 09 '19 at 15:23
  • 1
    ... However, I guess the real reason against implementing real numbers as a derivation of complex ones is efficiency. – Doc Brown Jan 09 '19 at 15:27
  • 3
    Possible duplicate of [Should rectangle inherit from square?](https://softwareengineering.stackexchange.com/questions/338787/should-rectangle-inherit-from-square) – BobDalgleish Jan 09 '19 at 17:21
  • @BobDalgleish (and gnat, and those that approved of their comments) The proposed duplicate is itself marked as a duplicate. While supported by the system, chains like that are rather confusing for readers - which page should we stop and read? – IMSoP Jan 09 '19 at 18:28
  • @IMSoP this is because duplicate closure is [not transitive](https://meta.stackoverflow.com/a/268485/839601). Your first stop and read is exactly the proposed duplicate. If you are interested in getting some extra information, you can (but not required to) follow to the next duplicate target – gnat Jan 09 '19 at 19:34
  • 1
    @gnat Meh, I disagree with both answers on that post. The banner on a duplicate reads "This question already has an answer here" not "you might also be interested in this other question", so it's natural for readers to stop there and never see the "non-transitive" content. Also, no new answers can be posted on a duplicate question, so if there is any useful content it's because it happened to become posted in time before it was closed. – IMSoP Jan 09 '19 at 20:17
  • Have you looked into how existing OO languages/libraries with complex numbers have handled this? – dan04 Jan 09 '19 at 22:18
  • 1
    If you're interested in using programming-language abstractions for maths concepts, I strongly recommend you also inform yourself about approaches beyond OO, in particular Hindley-Milner type systems like Haskell's. These are in many ways better suited for the task than OO hierarchies. – leftaroundabout Jan 09 '19 at 22:30
  • It's statistically unlikely that your language of choice implements them, but this would be a great application of dependent types. – 0xdd Jan 09 '19 at 22:31

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.

Bob Gilmore
  • 105
  • 3
Frank Puffer
  • 6,411
  • 5
  • 21
  • 38
  • 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 09 '19 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 09 '19 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 09 '19 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 09 '19 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
Caleth
  • 10,519
  • 2
  • 23
  • 35
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
  • 8
  • 1
    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 '19 at 09: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.

Craig Noah
  • 19
  • 5
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,335
  • 22
  • 28