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.

Compose Yourself 11

Posted by Andy Campbell,

Last time we had a small discussion about the merits of keeping inheritance hierarchies shallow and avoiding the creation of deep, multi-level structures. This is all fine and dandy, but how do we actually achieve this end? After all, many people are keenly aware of the DRY principle whether by name or just intuition. We do not like writing the same code twice. Most programmers have a keen distaste for the infamous copy/paste operation, realizing the shortsighted-ness of its use.

Therefore inheritance to the rescue!...Right? Not necessarily. Inheritance can seem to be built for code reuse but in fact it is not. It is built for abstraction. It is built for polymorphism. It is built to allow collaborative code which operates on the interface of the base class to be able to operate on more specific implementations of the interface. It is built not to reuse the code but to allow the interface of the base class to be used by other code.

So how do we then achieve the code reuse we desire? Keep calm, the classical approach is to favor composition instead. This means that we reuse pieces of code not by inheriting it but by holding onto a would be superclass as part of a would be subclass.

Thinking about celestial bodies like the last post, one way this could be achieved is as follows:

First we include our base class:

classdef CelestialBody
    
    properties
        Mass
    end
    
end

In order to include the rounded behavior in this class structure, lets include a RoundedBody class which contains no implementation.

classdef RoundedBody 
    
    properties(Abstract)
        Radius
    end   
    
end 

Since RoundedBody is purely abstract we are clearly not including it in our inheritance structure for code reuse purposes and our intent is much more clear.

Ok, now let's start creating some implementation:

classdef Sphere 
    
    properties
        Radius
    end  
    
end

Here we have a Sphere class which gives us the implementation we need and do not want to reimplement. Furthermore this class may have nothing to do with celestial bodies and is more centered around geometry, primitive shapes, and so forth. This class may even live outside of our celestial bodies application and in a shapes library. This is great news since the shapes library is a core library that is specifically intended for reuse and has broad application beyond just celestial body modeling. It has codified the best known way to handle shape operations, and has been battle tested and edge case proven. This is what we should be using.

...and we use it through composition:

classdef Star < CelestialBody & RoundedBody
    
    properties(Dependent)
        Radius
    end
    
    properties(Access=private)
        SphereDelegate = Sphere;
    end
    
    methods
        function radius = get.Radius(star)
            radius = star.SphereDelegate.Radius;
        end
        function star = set.Radius(star, radius)
            star.SphereDelegate.Radius = radius;
        end
        
        function radiate(star)
        end
    end
end

As you can see Star now exhibits RoundedBody behavior with a Sphere implementation. It is important to note the use of the Dependent property. For efficiency and maintainability reasons, we want there to be only one truth as to what the Radius is. Dependent properties allow this because there is no storage allocated for them, but they still behave like properties from the user's point of view. When user accesses or modifies them, MATLAB dispatches to the getter or setter, respectively. This is similar to getProperty or setProperty methods in a traditional OO language like C++ or Java® because the caller of C++/Java style get/set method wrappers doesn't know anything about the implementation. The caller does not know whether the getter is just accessing an allocated property of the instance, whether it is calculating it on the fly each time, or whether it is using a collaborator as we have done here. In this case the setter and getter delegate to the private SphereDelegate property. The user of the Star is none the wiser, and yet we have allowed code reuse of the Sphere code. Using Dependent properties for this operation in MATLAB as opposed to writing C++ or Java style set/get methods is very important because it allows powerful MATLAB indexing and vectorized operations on the property.

Here are a few more of our celestial modeling classes under the approach:

classdef Planet < CelestialBody & RoundedBody
    
    properties
        Moons
    end
        
    properties(Dependent)
        Radius
    end
    
    properties(Access=private)
        SphereDelegate = Sphere;
    end
    
    methods
        function radius = get.Radius(planet)
            radius = planet.SphereDelegate.Radius;
        end
        function planet = set.Radius(planet, radius)
            planet.SphereDelegate.Radius = radius;
        end
    end
end



classdef Moon < CelestialBody & RoundedBody
    
    properties
        HostPlanet
    end
    
    properties(Dependent)
        Radius
    end
    
    properties(Access=private)
        SphereDelegate = Sphere;
    end
    
    methods
        function radius = get.Radius(moon)
            radius = moon.SphereDelegate.Radius;
        end
        function moon = set.Radius(moon, radius)
            moon.SphereDelegate.Radius = radius;
        end
    end
end



classdef Asteroid < CelestialBody 
    
    properties
        Shape
    end
    
end

These classes together form the following structure:

As you can see this approach does lead to a bit more boiler plate code. Performing this delegation from a code writing perspective is not entirely free, although the extra code you do have to write is simple and not likely to contain bugs.

However, a key benefit we get here is the ability to separate the code we wish to share from the interface we wish to declare for ourselves. In our example, even though these classes have precisely the same functionality as Sphere, they are not substitutable for Sphere! Code which operates on these celestial bodies cannot, by design, just operate on any Sphere in the world, including Basketballs and Tomatoes. Similarly, these objects cannot be used in other environments that operate on Spheres and instead are restricted to the domain in which they are designed. Indeed it is even clear from the class diagram that the Sphere interface is not connected to the CelestialBody hierarchy. Composition is a much looser form of coupling than inheritance. While we still are able to reuse the code desired, we don't let these rounded celestial bodies interact with other software modules in ways that are neither designed nor understood.

What are your thoughts on this approach? Do you mind the boiler plate in exchange for the freedom and flexibility that composition provides?


Get the MATLAB code

Published with MATLAB® R2015a

11 CommentsOldest to Newest

kevin replied on : 1 of 11
This blog is awesome, and I'm glad that there's now a resource for using MATLAB in the same way that you would any mainstream OOP language.
Julian replied on : 3 of 11
Thanks for this post which has got me thinking a bit. A while back I came across this interesting contribution to the FeX DataFrame by Nicholas. If I am right it seems to me to be a great practical example of the principles you describe, but both you & Nicholas are somewhat in front of me here! I really like the idea of being able to extend the Table class in the way that Nicholas describes (just as soon as I get the chance to upgrade my daily use MATLAB to a version that supports tables, and migrate my extensive code-base away from dataset arrays!)
Andy Campbell replied on : 4 of 11
Hi Julian, Sorry for the delayed response! Thanks for passing along that FEX submission, it does indeed look like a good example of this principle. Sometimes people forget (myself included!) that you can benefit from other code in real concrete ways while still defining your own interface and allowing change down the road. For example, because Nicholas used composition, he can easily switch out the implementation of his class to use something other than table in the future. In fact, for your part, (and aided a little bit by hindsight which is not really fair!) you can imagine that in an alternate world where you used composition of datasets you might have been able to easily migrate your code away from datasets and onto tables easily by composing first datasets and then changing the implementation later to use tables when available. You could even have an adapter layer in place which used tables if the MATLAB version supported it but used datasets if not (and stats was available). Make sense? Hindsight is 20/20 though, things certainly don't always end up so nice and tidy. Thanks again for the insightful comment. Good luck! Andy
Ben replied on : 5 of 11
Hi Andy, Thanks for putting in the work for this blog! Could you elaborate on the sentence (and maybe give an example): "Using Dependent properties for this operation in MATLAB as opposed to writing C++ or Java style set/get methods is very important because it allows powerful MATLAB indexing and vectorized operations on the property."
Andy Campbell replied on : 6 of 11
Hey Ben, Sure thing. The key point to remember is that MATLAB is fundamentally an array based language and this applies to arbitrary objects just like it does to native data types like doubles/logicals, etc. The powerful vectorized/indexing operations I speak of can be demonstrated by looking at the array case. For example, with the example we are working with here lets say above we have an array of Planets. Lets add a Name property and create an array of these using preallocation:
        function planets = Planet(names)
            if nargin < 1
                % Allow a zero argument constructor to allow preallocation. When creating
                % an array each element will be constructed through this path.
                return 
            end
            names = cellstr(names);
            planets(numel(names)) = Planet; % Let MATLAB preallocate
            [planets.Name] = names{:}; % Now lets assign all the names
        end
Now we can leverage MATLAB's array nature to quickly and conveniently create the whole solar system!
>> planets = Planet({'Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune'})

planets = 

  1x8 Planet array with properties:

    Name
    Moons
    Radius
    Mass

>> 
Alright now lets assume that we have further initialized these planets so that their radii are correct. Now we can do some fun stuff with just the big ones. This leverages vectorization across the get method of Radius (the dependent property) as well as the set method of Moons if it is defined:
planets([planets.Radius] > 1e7).Moons = createLargeMoons
The above call accesses the get method of Radius for every element and we can easily turn that into a logical vector to actually modify a subset of the planet array, going through the set method of Moons. This is something that would be quite difficult to do when using C++ style setters/getters. Make sense? Also, a bit more info here.
Ben replied on : 7 of 11
It's still not quite clear to me where the benefits of having a dependent property come into play in terms of being able to use Matlab's vectorized operations. Say I had the following class (without dependent properties)
classdef Spaceship

    properties
        Length;
        Weight;
    end

    methods
        function value = get.Length(obj)
            % ... some getter logic
            value = obj.Length;
        end
        
        function obj = set.Length(obj, val)
            % ... some setter logic
            obj.Length = val;
        end
    end

end
of which I created an array and set 'Length' for every element. Now I can also use vectorized operations such as
[spaceships([spaceships.Length]>3).Weight] = deal(10);
Andy Campbell replied on : 8 of 11
Hey Ben, Yes you are correct. The comment about gaining the benefits of vectorization apply to *any* properties as you demonstrate. The original point I was making is that the C++/Java approach adds explicit getLength/setLength methods regardless of whether the class has a length field that is stored or calculated (i.e. a Dependent property). Not everyone fully realizes the power of using properties and property set/get methods with MATLAB this often results in people writing MATLAB code that follows ingrained practices of other languages. The reasons for these patterns on other languages do not apply with MATLAB and they can hinder the strengths of using MATLAB. In short using the property based approach is what I was saying was important, and making them dependent is what applied to that specific property due to the delegation that it demonstrates.
Ben replied on : 9 of 11
Ah great, thanks for clearing things up! Being foreign to the concept of dependent properties I wasn't sure if I was missing something important.