Developer Zone

Advanced Software Development with MATLAB

The Gift of Property Cheer

Well, it's that time of year, Christmas, Hanukkah, Kwanzaa, Ramadan, Winter Solstice, or you know.......Friday. You deserve the gift of a blog post. Here's a nugget of object oriented goodness in MATLAB, what better gift could there be?

Well perhaps a quick follow up discussion on some more benefits of MATLAB object properties as we have discussed a couple times before. This time I'd like to focus on an awesome (and relatively new) feature that allows for much more streamlined validation of properties.

First, a quick reminder, take a look at this special string class called FancyString that enables decorating individual elements it the string array with additional information such as hyperlinking or bolding. This starts with the FancyString itself:

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] = 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 s = string(fancyString)
            s = reshape([fancyString.PlainValue], size(fancyString));
        end
    end
end

As well as a couple decorators to add some fanciness:

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

end



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



classdef BoldDecorator
      
    methods
        function decoratedString = decorate(~, undecoratedString)
            decoratedString = "<strong>" + undecoratedString + "</strong>";
        end
    end       
    
    
end

Now we can have a nice message with some bolding and hyperlinking holiday cheer:

holidayMessage = ["Happy" "Holidays" "!"];

greeting = FancyString(holidayMessage);
greeting(1).Decorator = BoldDecorator;

% Add a link to US Holidays
holidayWikipediaSite = 'https://en.wikipedia.org/wiki/List_of_multinational_festivals_and_holidays#December';
greeting(2).Decorator = HyperlinkDecorator(holidayWikipediaSite);

join([greeting.DecoratedValue])
ans = 

    "Happy Holidays !"

Alright this is great, but there is a problem. Let's say we have an enumeration containing the different holidays and we'd like to put together a few different sets of greetings for our friends and neighbors with different preferences. Maybe this looks something like this:

classdef Holidays
    % Simple (and incomplete) enumeration of some winter holidays
    
    enumeration
        Christmas
        Hanukkah
        Kwanzaa
        Ramadan
        WinterSolstice
        NewYears
    end
end

That looks so nice I might just want to replace my string with one of these enumeration values. Seems reasonable.

greeting(2).PlainValue = Holidays.Kwanzaa
greeting = 

  1×3 FancyString array with properties:

    PlainValue
    Decorator
    DecoratedValue

So far so good. Quick spot check?

greeting.PlainValue
ans = 

    "Happy"


ans = 

    Kwanzaa


ans = 

    "!"

Still looks good at first glance. Alright how about the DecoratedValue? Do the hyperlinking and bold operations still work? (hint: the try-catch I've added in the script I am using to publish this blog is a hint that something fishy might be going on):

try
    greeting.DecoratedValue
catch e
    disp('Errored!');
    disp(e.getReport('basic'));
end
Errored!
Undefined function 'plus' for input arguments of type 'Holidays'.

Alright this doesn't work because I've assigned to the PlainValue a value from our enumeration, but the FancyString (and the decorators) are expecting the value to be a string.

In fact, we could have supplied any arbitrary value to the property and we likely wouldn't have noticed until much farther down the line. For example, let's add a structure, which is meaningless in this context:

meaninglessStruct.SomeField = 5;
meaninglessStruct.AnotherField = true;

greeting(2).PlainValue = meaninglessStruct; % Assignment works!!
greeting.PlainValue % Still doesn't error!
ans = 

    "Happy"


ans = 

  struct with fields:

       SomeField: 5
    AnotherField: 1


ans = 

    "!"

It only errors when we use it farther down the line, such as in concatenation:

try
    [greeting.PlainValue]
catch e
    disp('Errored!');
    disp(e.getReport('basic'));
end
Errored!
Error using string
Conversion to string from struct is not possible.

This is clearly not optimal. Property validation to the rescue! We can make the simplest of tweaks to the FancyString so that we can always know for sure that the PlainValue property will always be a string. It's as easy as this slightly more fancy string implementation:

classdef MoreFancyString 
    
    properties
        
        % Property validation for the win! Let's guarantee the PlainValue
        % is always a string
        PlainValue (1,1) string
        
        Decorator = DefaultDecorator; 
    end
    
    properties(Dependent)
        DecoratedValue
    end
    
    
    methods
        function fancyString = MoreFancyString(varargin)
            if nargin < 1
                return
            end
                        
            plainValue = string(varargin{:});
            fancyString(numel(plainValue)) = MoreFancyString;

            plainValueCell = num2cell(plainValue);
            [fancyString.PlainValue] = 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 s = string(fancyString)
            s = reshape([fancyString.PlainValue], size(fancyString));
        end
    end
end

The only difference here is in the property block where plain value was defined:

PlainValue (1,1) string

This states that the PlainValue must always be a scalar string, which gives us much more peace of mind as we process that code. How does it work now?

greeting = MoreFancyString(holidayMessage);
greeting(1).Decorator = BoldDecorator;
greeting(2).Decorator = HyperlinkDecorator(holidayWikipediaSite);
greeting(2).PlainValue = Holidays.Kwanzaa
greeting = 

  1×3 MoreFancyString array with properties:

    PlainValue
    Decorator
    DecoratedValue

This time if you look closely you will see the enum was converted to a string

greeting(2).PlainValue
ans = 

    "Kwanzaa"

...and because of that everything else like concatenation and the string decoration works as expected:

join([greeting.PlainValue])

join([greeting.DecoratedValue])
ans = 

    "Happy Kwanzaa !"


ans = 

    "Happy Kwanzaa !"

It also correctly errors right away with bunk values that don't know how to be represented as strings:

try
    greeting(2).PlainValue = meaninglessStruct;
catch e
    disp('Errored!');
    disp(e.getReport('basic'));
end
Errored!
Error using y2017PropertyTypes (line 138)
Error setting property 'PlainValue' of class 'MoreFancyString':
Invalid data type. Value must be string or be convertible to string.

Awesome! A couple concluding thoughts:

  1. We could have also given this guarantee by implementing a set.PlainValue method that validates the input using the validateattributes or otherwise. This would be certainly possible, and a benefit of encapsulated properties. However, if this validation is all that is needed property validators are much more convenient and accessible.
  2. Classes that are convertible to the datatype specified (in this case string) are automatically converted. In this case the enumeration really knows how to be interpreted correctly as a string, and so it just intuitively worked out of the box!
  3. This conversion has a flipside. What this really provides is a guarantee that the actual stored value will be a string. However,you can set non-string values to this property as long as they are convertible to a string, and the conversion will be performed upon setting the property. This is typically the right behavior, since the value with a type conversion is the class that should be able to decide that it can be interpeted as a string. However, if you find that you wish to disallow any datatype from being assigned to the property unless it is the precise type to start with, then you may need to leverage the setter method instead to enforce that restriction directly.

Have you used the new property validation features? I hope so. Actually there is much more richness to property validation than I've shown here, check out the documentation. I've hinted at size/shape validation but there is also the ability to pass validation functons to the value being set, as well as a rich library of validation functions in MATLAB for your benefit. Let us know what you think!....oh, and

holidays = enumeration('Holidays');
for idx = 1:numel(holidays)
    greeting(2).PlainValue = holidays(idx);
    if strcmp(holidays(idx), 'Christmas') % There's always a special case
        specialGreeting = greeting;
        specialGreeting(1).PlainValue = "Merry";
        disp(join([specialGreeting.DecoratedValue]));
        continue;
    end
    disp(join([greeting.DecoratedValue]));
end

Merry Christmas !
Happy Hanukkah !
Happy Kwanzaa !
Happy Ramadan !
Happy WinterSolstice !
Happy NewYears !




Published with MATLAB® R2017b

|
  • print

评论

要发表评论,请点击 此处 登录到您的 MathWorks 帐户或创建一个新帐户。