Common Design Considerations for Object Properties
I’m pleased to introduce guest blogger Dave Foti. Dave has worked on MATLAB for over 15 years and currently manages the group responsible for object-oriented programming features in MATLAB. He is interested in giving MATLAB users the tools to meet increasingly complex challenges. This post will take a look at making use of object properties in MATLAB.
Contents
Property Basics
Properties are how MATLAB objects store data. At their most basic level, properties provide a way to define what data are required for a particular kind of object. For example, if I have a set of measurements and each measurement has the same pieces of information, then I can create a class of measurement objects with a property to store each piece of information as in:
type surfaceTempReading1
classdef surfaceTempReading1 properties Date Latitude Longitude Temperature end end
I might have an instance that looks like this:
t1 = surfaceTempReading1; t1.Date = date; t1.Latitude = '42:16:4 N'; t1.Longitude = '71:20:2 W'; t1.Temperature = 260; disp(t1);
surfaceTempReading1 Properties: Date: '16-Mar-2011' Latitude: '42:16:4 N' Longitude: '71:20:2 W' Temperature: 260
The MATLAB documentation describes properties here.
Dependent Properties
In simple cases like this, it is probably fine to have all properties directly store and retrieve data. However, MATLAB provides ways to define properties that don’t directly store data. These dependent properties are defined using the Dependent attribute as in:
type surfaceTempReading2
classdef surfaceTempReading2 properties Date Latitude Longitude Temperature end properties(Dependent) Altitude end methods function A = get.Altitude(reading) A = lookupAltitude(reading.Latitude, reading.Longitude); end end end
In this case, if I have a data set of surface temperature readings, I just need latitude and longitude to identify where the temperature was sampled. However, in analyzing the data, it might be helpful to correlate temperatures with surface altitude. Rather than have to input this data, the altitude can be calculated from a database of Earth surface topography. Assume I have a function that can provide such a value called lookupAltitude, then I can invoke that function from the Dependent property called Altitude.
t2 = surfaceTempReading2; t2.Date = date; t2.Latitude = '42:16:4 N'; t2.Longitude = '71:20:2 W'; t2.Temperature = 260; disp(t2)
surfaceTempReading2 Properties: Date: '16-Mar-2011' Latitude: '42:16:4 N' Longitude: '71:20:2 W' Temperature: 260 Altitude: 342
Changing Property Names
Dependent properties allow me some flexibility to implement the same external interface in different ways. A user of our class doesn’t necessarily need to know how altitude was determined or whether or not it is actually stored in a class instance. This flexibility can be useful in evolving a class over time while not breaking scripts and functions that might use the class. For example, if I decide that TimeStamp is a better name for the Date property, I can gradually start switching over to the new name without immediately changing all of the code I have using Date:
type surfaceTempReading3
classdef surfaceTempReading3 properties TimeStamp Latitude Longitude Temperature end % Deprecated property names properties(Dependent, Hidden) Date end methods function D = get.Date(reading) D = reading.TimeStamp; end function reading = set.Date(reading, D) reading.TimeStamp = D; end end end
When I change my class as above, not only will old scripts using Date continue to work, but old MAT-Files saved using the old class definition will automatically load the Date value into the new TimeStamp property. I use the Hidden attribute so that the old property name doesn’t show up in object display, the properties command, or tab completion.
t3 = surfaceTempReading3; t3.Date = date; disp(t3);
surfaceTempReading3 Properties: TimeStamp: '16-Mar-2011' Latitude: [] Longitude: [] Temperature: []
Side Effects and Validation
Dependent properties can also be useful any time a property has side effects – when for example changing a property triggers an update to some other object or graphic or when getting a property requires doing some expensive operation to acquire or validate the result. Often these expensive operations can be avoided from certain "trusted" code defined inside the class. Having a one private property that doesn’t perform any side effects allows the trusted code to work on the raw property value. Other code inside and outside the class can use a public and dependent property that performs the side effects or expensive operations. Sometimes the expensive operation is just validating the input value being assigned to the property. For example, in our class if I want to validate temperature values, I can do something like the following:
type surfaceTempReading4;
classdef surfaceTempReading4 properties Date Latitude Longitude end properties (Dependent) Temperature end properties(Access=private) pTemperature end methods function reading = set.Temperature(reading, t) if t < 0 error('SurfaceTemp:BadTemp', ... 'Temperature must be a positive number in Kelvins.'); end reading.pTemperature = t; end function t = get.Temperature(reading) t = reading.pTemperature; end end end
NOTE: The above example has been changed to correct an error in the original post.
t4 = surfaceTempReading4; try t4.Temperature = -15; catch err disp(err.message); end
Temperature must be a positive number in Kelvins.
Default Values
Classes can define default values for properties. A default value is defined by the class and each instance of the class is assigned that default value. Default values can be used when there is a constant default that can be documented with the class definition. Default values can save space in MAT-Files because defaults are saved once per class and values of properties not differing from default values don’t need to be saved. Generally speaking default values should be literal values wherever possible so that it is clear what the default is. For example:
type surfaceTempReading5
classdef surfaceTempReading5 properties Date Latitude = '0N'; Longitude = '0E'; Temperature = 0; end end
While it is possible to use the result of a function for a default value, one should avoid doing so unless the function returns a constant value. For example, it might be tempting to do something like:
type surfaceTempReading6
classdef surfaceTempReading6 properties % Get the current date from MATLAB Date = date; Latitude Longitude Temperature end end
However, the problem with this is that the current date is not a constant default value that can be documented as part of the class. Moreover, since MATLAB evaluates default property expressions in class definitions only when a class is loaded or reloaded, the function call will generally happen once in a MATLAB session. Each object will get the same date stamp that was current when the class was loaded and not the current date at the time an object is created (perhaps the next day). Generally, one doesn’t want to assign a default value to a handle for a similar reason. If the intent is to make a new handle for each instance, then this handle has to be created in the constructor and not as a default value. Since defaults are the same for all instances created from a given class definition, all objects would get the same handle. For more information on handle classes, see Handle and Value Classes.
I’ve described a few uses for properties and a few common attributes, but I would be very interested in hearing how you use properties. You can post ideas here.
- 범주:
- Object-oriented