Developer Zone

Advanced Software Development with MATLAB

This is machine translation

Translated by Microsoft
Mouseover text to see original. Click the button below to return to the Original version of the page.

Don’t Get In Too Deep 13

Posted by Andy Campbell,

The conventional wisdom these days seems to be that it is better to go deep, not wide. Whether this is in education, marketing, or general approaches to focused living, there seems to be an theme these days that if you favor breadth over depth the result will be shallow, without substance, and spread thin. The generalist it seems has seen better days in the court of public opinion. Is that true in life? I dunno, perhaps.

Well I'm here to denounce this approach when it comes to class design! Unfortunately, class hierarchies seem to find a way over time to get deeper and deeper. As these hierarchies grow deeper the coupling increases, and inheritance is a very strong form of coupling. The interface grows with each extension point, meaning that a class with 18 levels of superclasses likely has at least 18 methods which are called and used by even more clients. Often these clients only operate on a small part of the interface and yet an accidental coupling inevitably creeps in because it can. All clients have access to the entire interface rather than just the one thing they asked for.

Deep hierarchies also rely heavily on the categorization occurring correctly down through the subclasses and the generalization up through the superclasses. In theory this works great, because, well, we humans are naturally adept at categorizing things right? (*cough cough* platypus *cough cough*)

OK, so a platypus is a classic poster child for categorization gone wrong. Mammals are supposed to give live birth, right? Well, no of course, but this may be an easy mistake to make. You might say that a taxonomy which states that all mammals give live birth is an incorrect taxonomy, not a fundamental problem of taxonomies in general, and you'd be right. However, once a piece of software starts connecting and integrating with the rest of the software system changing your classification becomes difficult because you have clients that start to depend on this classification. Said another way, the 19 century taxonomy that did not correctly account for the platypus could be adjusted and corrected with the new information presented to it. However, it is not so easy to change software systems that are depended on by many downstream clients. Often times in software poor categorization decisions need to be lived with and worked around for a long time.

This happens All.The.Time.

Deep Space

For example, take a look at a simple model of some celestial bodies. First let's start with the following classes which seem like a reasonable categorization:

classdef CelestialBody
    properties
        Mass
    end
end

classdef GravitationallyRoundedBody < CelestialBody
    properties
        Radius
    end
end

classdef Asteroid < CelestialBody
    properties
        Shape
    end
end

classdef Moon < GravitationallyRoundedBody
    properties
        HostPlanet
    end
end

classdef Star < GravitationallyRoundedBody
    methods
        function radiate(star)
        end
    end
end

classdef Planet < GravitationallyRoundedBody
    properties
        Moons
    end
end

classdef TerrestrialPlanet < Planet
    properties
        Crust
    end
end

classdef RingedPlanet < Planet
    properties
        Rings
    end
end

classdef GasGiant < RingedPlanet
end

classdef IceGiant < RingedPlanet
end

I went over to the file exchange and downloaded the UMLgui submission so that we could visualize these classes a bit more easily (Hint you should too!). With this UI we can generate the following class diagram from this simple code:

Everything here at first glance seems entirely rational. Go ahead and pick any spot in that hierarchy and ask yourself whether each class "isa" more specialized version of its superclass. Similarly, if you are a fan of Liskov there doesn't seem to be any obvious substitutability violations.

With This Ring...

However, we can easily poke some holes into this simple structure. For example, what about the rings? Well, this model worked quite well when you think about the planets in our own solar system, it is indeed the gas/ice giants that have rings so it seems to work well...that is until NASA discovers that Rhea may have some rings!

Now we are placed in an unfortunate situation that is common with deep inheritance hierarchies. The problem here is that Rhea is a moon! We may have some code that operates on celestial bodies with rings and we'd like to support Rhea in such code. However, the class which defines what it means to have rings is actually a planet. Rhea can't (shouldn't!) derive from RingedPlanet because that would give Rhea moons! Furthermore, we can't move RingedPlanet higher in the hierarchy to allow Rhea to access this feature because then TerrestrialPlanet and Star would have rings, not to mention the fact that this would apply to all moons, not just rare moons like Rhea which (might?) contain them.

We can play this game with other features of the interface as well. What about crust? Most moons have a crust like the terrestrial planets, but how do we include the crust both above the Planet interface so that moons have access to this property as well as below the Planet interface so that the gas/ice giants don't inherit it. How might we add an atmosphere to these models? The planets have one (oops! not Mercury) and the moons don't (wait, what about Titan?). Don't even think about looking outside of our own limited solar system, this structure will fall apart quickly. When we try to structure our classes in a pure hierarchy we inevitably fail as we see new requirements and try to apply our software structure to new features and ideas.

This example demonstrates the principle of favoring class hierarchies that are wide rather than deep. Shallow and wide hierarchies contain concrete classes that are coupled to a low number of superclasses. They can opt into precisely the interfaces they need without adding unnecessary baggage. These interfaces themselves can be small, scoped, and modular such that subclasses can pick and choose just the pieces of the interface that apply directly to them.

We can make this class structure much more flexible by flattening the hierarchy and bringing some of these key abilities into their own independent base classes. For example we can remove two levels of hierarchy and solve the problems discussed by simply pulling the gravitationally rounded properties and whether the body has rings their own independent interfaces. This looks like this:

classdef CelestialBody
    properties
        Mass
    end
end

classdef Rounded
    properties
        Radius
    end
end

classdef Asteroid < CelestialBody
    properties
        Shape
    end
end

classdef Moon < CelestialBody & Rounded
    properties
        HostPlanet
    end
end

classdef Star < CelestialBody & Rounded
    methods
        function radiate(star)
        end
    end
end

classdef Planet < CelestialBody & Rounded
    properties
        Moons
    end
end

classdef TerrestrialPlanet < Planet
    properties
        Crust
    end
end

classdef HasRings
    properties
        Rings
    end
end

classdef GasGiant < Planet & HasRings
end

classdef IceGiant < Planet & HasRings
end

This code produces the following hierarchy:

With this structure, it becomes trivial to give a moon some rings!

Now there are some different strategies for flattening these hierarchies, but hopefully I've motivated the desire to keep them flat in the first place. We can work through some strategies in another post.

Do you have any deep hierarchy horror stories? How about feel good romances with nice shallow designs? Love to hear about em...


Get the MATLAB code

Published with MATLAB® R2015a

13 CommentsOldest to Newest

Sam Roberts replied on : 1 of 13
Hi Andy, Great article again - thanks. A small typo, I think: in your second code block, you have HasRings inheriting from Planet, which I don't think you wanted and isn't reflected in the diagram. I would naturally have made Rounded and HasRings into Abstract classes (even though they have no abstract properties or methods), but now that I think about it I'm not sure why. It seems right to me, because they don't really represent "things", but are really just guarantees that a more concrete thing that implements them would have particular properties, and it wouldn't make sense to instantiate them directly. Is that a good design principle in MATLAB? In other languages such as Java I guess you might use an interface to do that - but then Java doesn't allow multiple inheritance as MATLAB does. Can you compare/contrast the two languages (sorry, it sounds like I'm asking for an essay). On a related note, since MATLAB is such an array-oriented language, you're likely to want to be able to make arrays that collect together Stars, Planets, Moons etc - and to do this, they all need to inherit from a single root class that inherits from matlab.mixin.Heterogeneous. In your first example, that root class would naturally be CelestialBody - but in the second example it's not really possible, as things also inherit from HasRings and Rounded. What's the best design in that circumstance?
Andy Campbell replied on : 2 of 13
Thanks Sam, you are right about the typo and I've fixed it. Your other comments make me smile because it seems I have succeeded in priming the pump on this topic. These questions are precisely the type of discussion points that I'd like to follow up on in subsequent posts. Stay tuned and we'll hopefully cover things like differences between MATLAB and traditional OO languages like C++ & Java. Please chime in there as well. As for the Heterogeneous question, it actually does work in the second structure even when CelestialBody is Heterogeneous yet Rounded & HasRings are not explicitly Heterogenous. Try it out!
Sam Roberts replied on : 3 of 13
Thanks Andy. It seems you're right about Heterogeneous - that's going to change the way I think about things. May I suggest that the doc page for Heterogeneous could do with a review? Every time before now, I've read it as saying that if you wanted Heterogeneous, your classes could derive _only_ from a single root class. Now that I reread it, the wording does seem to allow that so long as you do derive everything from a single root class, you can derive from other classes as well. But - just my 2c - it still feels like it could maybe be worded more clearly. Looking forward to those future posts on design topics...
Andy Campbell replied on : 6 of 13
Thank you for the kind words Eric. We are always looking for ways to improve the doc so thanks for the suggestion.
David Barry replied on : 7 of 13
Hi Andy, Any plans to develop a builtin UI to help visualise class diagrams rather than relying on a FileExchange tool (there are quite a few out there)? I've mentioned to a number of your colleagues in the UK MathWorks office that this is a long overdue feature. Regards
Andy Campbell replied on : 8 of 13
Hi David, Great suggestion, I agree that would be very useful! I will connect with a few development teams to find out if they have any such plans and if not I will encourage them to consider providing one. That would indeed be very nice.
David Barry replied on : 9 of 13
Thanks Andy. It would also be nice to take the tool a step further and allow it to be used to design UML class diagrams and then have the MATLAB classdef skeleton auto-generated. We currently use Enterprise Architect for UML but are left to manually transform the class diagrams in to MATLAB when it comes to coding up. I realise I am asking for a lot!
MATLAB Central - Developer Zone » Compose Yourself replied on : 10 of 13
[…] 31 Mar Don't Get In Too Deep […]