4

On one hand

class complex {
    double real;
    double imaginary;
}

it is composition

enter image description here

on the other hand

class complex : double {
    double imaginary;
}

it is inheritance

enter image description here

Which one is correct? Why arrows go opposite direction, denoting the same thing? Who invented all this?

8
  • 1
    Are you asking if to use composition or inheritance or why the line symbols are on opposite sides?
    – Euphoric
    Apr 22 '16 at 11:13
  • 2
    In mathematical terms, real numbers are just a special case of complex numbers (a+bi) where b is zero. Real numbers are complex numbers, in the same way that rational numbers are real, and integers are rational. In some sense, real numbers should inherit from complex numbers, but that's probably not possible. Really, the answer is just to not worry about it and just use composition.
    – Kevin
    Apr 22 '16 at 12:09
  • @KevinMills math is very different from programming here in the sense that math objects are immutable. In the context of programming, establishing relation between real and complex numbers would be rather "a category error or category mistake" as was pointed in this answer to similar (possibly diplicate) question
    – gnat
    Apr 22 '16 at 12:16
  • 1
    @gnat I am aware. Which is why I suggested not to use inheritance.
    – Kevin
    Apr 22 '16 at 12:21
  • 5
    Which is your question? Are you looking for the "correct" model? An explanation of why the arrows go in opposite directions? Or who invented the notation you're using?
    – MetaFight
    Apr 22 '16 at 12:21
3

Graph(ical) issues aside, the inheritance case seems just wrong to me. A complex number consists of two data members, and I don't see any advantage in inheriting one and adding the other. Even worse, you may be able to do invalid calculations with a complex and a non-complex number (double or integer) when deriving from double, while I think these operations must be encaspulated in methods when valid. Using composition the compiler will just refuse to do it, until you overload an operator with the (hopefully correct) implementation.

"Explicit is better than implicit."

1
  • This is all true, but what about UML?
    – Dims
    Apr 22 '16 at 12:12
2

The association marker isn't an arrow, it's a binary relation that is marked on one side (so you can distinguish dog-has-tail from tail-has-dog). And the inheritance arrow looks as if it is "moving" into one particular direction, but there is no particular reason why anything would notionally move from the superclass to the subclass, or from the subclass ot the superclass. Again, it's just a means of marking the asymmetric relation, which is necessary because the difference between "A is-a B" and "B is-a A" is important.

More generally, these notations are largely arbitrary in the same way that writing "A" for one particular vowel sound and "B" for a particular consonant is arbitrary: none of these signs has any special affinity for the thing they denote, and we might just as well have chosen different symbols. The important thing is that now everyone agrees how to write down sounds.

Similarly, in OOP we now largely agree how to draw a class diagram, and this allows us to communicate more efficiently, even when the devices we use for notation are not especially compelling. Wondering why one kind of arrow stands for one thing and another kind for another thing can only teach you about the history of UML, not actually about software development, therefore it kind of misses the point.

1

To quote Eric Evans, "a model is neither right nor wrong, just more or less useful"

An inheritance hierarchy could be useful if you really need to operate on many different types of numbers and be able to handle them in an abstract manner. In this case maybe you also need Fraction class, and maybe the abstract base class is neither Complex, nor double but a Number class. A Complex would of course consist of two Number instances. All math operators would take numbers as input and return number instances, but the actual return type would depend on the input types. Divide two doubles and you get a double. Divide two integers and you get a Fraction. Multiply a fraction by its denominator, and you get an integer.

But I have never worked with an application where such an abstraction is useful. If you are operating on complex numbers, the functions that need complex numbers know they need complex numbers, not some base abstraction.

Operator overloading would probably be useful (if you language supports it), allowing you to easily add or multiply complex and floating point numbers. But being able to operate on them in an abstract manner is rarely necessary.

0
1

Your first UML model says that a complex number is composed of 2 doubles. This is definitively an acceptable way to it.

Your second UML model does not represent the corresponding code: you don't show inheritance but a navigable association, and you forget to tell about the additional member. The following would more accurately represent the code:

enter image description here

However, this does not represent the reality: a complex is not a kind of double. For example, if x is a double, I can always assume that x*x >= 0. So for any subtype of double, I'd assume that this fundamental assumption remains true. This is exactly what Liskov's substitution principle means with its rule about post-conditions. So, as the square of a complex can be negative, the complex is not a proper subtype of a double. Transposed to OOP, complex should not inherit from a double.

Your second option however unveils a fealing that there must be something in common between doubles and complex. Well, they are both numbers. And this you can very well express with the following model:

enter image description here

Now, you may just continue, with modelling the number theory, starting with defining the number interface. This could be a nice step if you want to write a symbolic calculator


Additional note: Liskov's substitution principle was first defined by Barbara Liskov and Jeanette Wing, long before R.C. Martin took it over into his SOLID principles. It's not a dogmatic OOP rule, but a fundamental work on typing theory. Bjarne Stroustrup in his book "The design and evolution of C++" acknowledged for example that protected members allows to shortcut Liskovs's history constraint and that this was the cause of very nasty bugs that are hard to debug. So it's definitively not an obsolete principle;-)

0

Why not make real a special case of complex where imaginary just happens to be 0. This obeys the substitution principle which states that a subclass must always be able to stand in for the superclass. This means that real should inherit from complex (because a real number is a complex number).

In any case I would not worry too much over it and just ensure that I could convert to complex from real.

1
  • 2
    Unless we're talking immutable objects here, the substitution property is not given, since we would expect to be able to set the imaginary part of the complex number to something, but if the actual object is just a real number, that's not possible. Also, design issues aside, this would just be an efficiency nightmare, since all your real numbers just doubled in size (before counting the overhead for being a class). Apr 22 '16 at 12:23
0

Unfortunately, it is difficult to model the relationships between numeric types using the typical feature set of an object oriented language. You can get close by modelling both real and complex as instances of an interface" number", but OO semantics will trip you up because in many cases functions of real values will want to return real and the same functions will want to return complex for complex arguments. You'll have to compromise and just return numbers without specifying which, but that ends up with a loss of type information (at least for static languages), which may be undesirable.

A better answer is the use of polymorphic functions, rather than classes, along with some kindof constraint system to ensure your type arguments are proper numeric types. This is the way many functional languages handle the problem, and if c++ ever gets the template concept constraint system the c++ committee have been working on for years, you'd be able to achieve a similar result in c++. Otherwise, ad-hoc overloading is probably the best that can be achieved.

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.