Developer Zone

Advanced Software Development with MATLAB

Properties of properties 8

Posted by Andy Campbell,

A while ago we discussed a good reason to appreciate and use MATLAB's model for properties. It had to do with encapsulation if you remember, and we discussed how MATLAB's properties are inherently encapsulated, preventing the need to write a bunch of boiler plate setters and getters.

There's more goodness where that came from! Another reason to use MATLAB's properties is to benefit directly from the fact that everything in MATLAB is considered an array that can be sliced and diced however you please.

The beauty if MATLAB is in its simplicity of handling arrays. Rather than requiring some external container like a List or a Vector of something, everything is inherently already a list and a vector! With this you can be free to vectorize to your heart's content, as well as index into your array using the powerful MATLAB syntax we all know and love.

This gets us back to object properties. If you use properties instead of setter and getter methods you benefit from all this indexing sugar for free.

Pulling on strings

Case in point: Let's say we love the new string class in MATLAB. After all what's not to love? The new MATLAB strings are rational and array oriented, opening us to fast and powerful manipulation techniques. A quick point is that we can indeed leverage this "arrayness" directly in the string class itself without writing our own object code. For example, if you wanted to quickly do some manipulation on a given string in order to get your priorities straight you can do so quite easily. Let's start by creating a string scalar:

openingLine = "It was the best of times it was the worst of times"
openingLine = 

    "It was the best of times it was the worst of times"

This is great, we can operate on this as a single string rather than the old character vector approach where each character is an element of the array. However, right now, I'd like to operate on this as an array of words, so let's split it up:

openingLine = split(openingLine)
openingLine = 

  12×1 string array

    "It"
    "was"
    "the"
    "best"
    "of"
    "times"
    "it"
    "was"
    "the"
    "worst"
    "of"
    "times"

Now that this is an array of words, we can easily have it conform to our nefarious demands! We can replace "times" with "blogs" simply using the power of indexing:

openingLine(contains(openingLine, "times")) = "blogs"
openingLine = 

  12×1 string array

    "It"
    "was"
    "the"
    "best"
    "of"
    "blogs"
    "it"
    "was"
    "the"
    "worst"
    "of"
    "blogs"

Note for MATLAB strings the replace function is actually easier than this, but I wanted to show the broader context of array indexing. In practice with strings we should use replace.

openingLine = "It was the best of times it was the worst of times"
openingLine = split(openingLine)
openingLine = replace(openingLine, "times", "blogs")
openingLine = 

    "It was the best of times it was the worst of times"


openingLine = 

  12×1 string array

    "It"
    "was"
    "the"
    "best"
    "of"
    "times"
    "it"
    "was"
    "the"
    "worst"
    "of"
    "times"


openingLine = 

  12×1 string array

    "It"
    "was"
    "the"
    "best"
    "of"
    "blogs"
    "it"
    "was"
    "the"
    "worst"
    "of"
    "blogs"

Make it your own

Alright, vectorization and indexing are powerful in the MATLAB environment, but how do we leverage this in our own objects? The answer is to use properties! Let's say we want to create our own fancy string class which adds to the capabilities of the string class we see in MATLAB. Perhaps we want to keep the underlying text content of the string pure an unchanged, but we want to add specific meta-data to individual elements. For example, in environments that support hyperlinks (like the MATLAB command window) I may want to add a hyperlink, but I don't want this hyperlink to modify the real text content. Maybe I'd like to print that to a text file and the hyperlink would get in the way. Let's look at how we can do this, and benefit from the indexing behavior of MATLAB's properties.

First, we should create our FancyString class. Note we leverage composition over inheritance (remember?) and we hold on to the "plain" string. However, the constructor passes all values through to the string constructor so that it feels like a normal string. Note we can also add converter methods for char, string, and cellstr so that we can combine these values together effortlessly.

classdef FancyString 
    
    properties
        PlainValue
    end

    
    methods
        function fancyString = FancyString(varargin)
            if nargin < 1
                % Supporting a zero argument constructor allows us to
                % preallocate. This is very powerful when creating arrays.
                % Note this constructor itself preallocates below.
                return 
            end
                        
            % Create the plain string
            plainValue = string(varargin{:}); 
            % Create the array of FancyStrings to match. 
            fancyString(numel(plainValue)) = FancyString; 

            % Each element of this FancyString array holds onto (composes)
            % a regular string scalar. Send each element of the string
            % array to each element of the FancyString array
            plainValueCell = num2cell(plainValue);
            [fancyString.PlainValue] = deal(plainValueCell{:});
        end
            
        function c = char(fancyString) % Allow conversion from char
            c = char(fancyString.PlainValue);
        end
        function c = cellstr(fancyString) % Allow conversion from cellstrs
            c = cellstr({fancyString.PlainValue});
        end
        function s = string(fancyString) % Allow conversion from strings
            s = [fancyString.PlainValue];
        end
    end
end

Does this work? Let's try it out with our string

fancyString = FancyString(openingLine);
[fancyString.PlainValue]'
ans = 

  12×1 string array

    "It"
    "was"
    "the"
    "best"
    "of"
    "blogs"
    "it"
    "was"
    "the"
    "worst"
    "of"
    "blogs"

Alright, so far so good. Now we can start adding our fanciness! What I'd like to do is add the ability to decorate each string element with a decorator, and introduce the notion of a decorated value of the string. It is here I'd like to add the hyperlinks. Then, when I want to send the output to the Command Window I send the decorated value, but when I want to send the output to a text file I print the plain text. First's let's add the notion of a default decorator, which does nothing when decorated:

classdef DefaultDecorator
    
    methods
        function decoratedString = decorate(~, undecoratedString)
            decoratedString =  undecoratedString;
        end
    end       

end

Now let's add a hyperlink decorator which decorates the string with a hyperlink tag. In this case we need the link target, but the link text should be taken as the value of the underlying string. I love how we can simply add these strings together to do this decoration!

classdef HyperlinkDecorator
   
    properties
        LinkTarget
    end
    
    methods
        function decorator = HyperlinkDecorator(linkTarget)
            decorator.LinkTarget = linkTarget;
        end
        function decoratedString = decorate(decorator, undecoratedString)
            decoratedString = "<a href=""" + decorator.LinkTarget + """>" + undecoratedString + "</a>";
        end
    end       
    
    
end

Alright, we are now set with our two decorators, let's use them with the FancyString:

classdef FancyString 
    
    properties
        PlainValue
        
        % Each element uses the "do nothing" decorator by default
        Decorator = DefaultDecorator; 
    end
    
    properties(Dependent)
        DecoratedValue
    end
    
    
    methods
        function fancyString = FancyString(varargin)
            if nargin < 1
                return
            end
                        
            plainValue = string(varargin{:});
            fancyString(numel(plainValue)) = FancyString;

            plainValueCell = num2cell(plainValue);
            [fancyString.PlainValue] = deal(plainValueCell{:});
        end

        function decorated = get.DecoratedValue(fancyString)
            % The decorated value returns whatever the decorator adds to
            % the plain string.
            decorated = fancyString.Decorator.decorate(fancyString.PlainValue);
        end
            
        function c = char(fancyString)
            c = char(fancyString.PlainValue);
        end
        function c = cellstr(fancyString)
            c = cellstr({fancyString.PlainValue});
        end
        function s = string(fancyString)
            s = [fancyString.PlainValue];
        end
    end
end

We've added a Decorator property as well as a dependent DecoratedValue property. Since MATLAB is built for arrays this property is inherently array oriented. Each element of the FancyString has it's own Decorator. Also, when we access the DecoratedValue property on the array, each element of the array get's it own call to the get.DecoratedValue function to return the correct property. Let's see this beauty in action.

First we can create the FancyString again, but this time add a decorator to make it interesting. Let's add a hyperlink to the blog everywhere we see the word "blogs" in the plain value.

fancyString = FancyString(openingLine);
[fancyString(contains([fancyString.PlainValue], "blogs")).Decorator] = deal(HyperlinkDecorator("https://blogs.mathworks.com/developer"));

Using MATLAB properties when writing the class, we've just leveraged indexing to add the desired hyperlink to those elements of the FancyString that contain the word blogs. Did it work?

decoratedValue = join([fancyString.DecoratedValue])
decorator = fancyString(end).Decorator
decoratedValue = 

    "It was the best of blogs it was the worst of blogs"


decorator = 

  HyperlinkDecorator with properties:

    LinkTarget: "https://blogs.mathworks.com/developer"

Note the real underlying string is still available, unchanged and ready to display as well

plainValue = join([fancyString.PlainValue])
plainValue = 

    "It was the best of blogs it was the worst of blogs"

Are you leveraging MATLAB properties effectively in your MATLAB objects? Are you leveraging the inherent benefits of their arrayness? Do tell!


Get the MATLAB code

Published with MATLAB® R2017a

32 views (last 30 days)  | |

Comments

To leave a comment, please click here to sign in to your MathWorks Account or create a new one.