Dynamically assign the getter for a dependent property in MATLAB
dynamic-properties, matlab, oop
Solution
Here is my proposal: create a method in the superclass called `add_dyn_prop`. This method is to be called in the subclasses instead of creating a dependent property the usual way.
The idea is that the superclass inherit from `dynamicprops` and use `addprop` to add a new property, and set its accessor methods manually based on its name.
classdef klass < dynamicprops
methods (Access = protected)
function add_dyn_prop(obj, prop, init_val, isReadOnly)
% input arguments
narginchk(2,4);
if nargin < 3, init_val = []; end
if nargin < 4, isReadOnly = true; end
% create dynamic property
p = addprop(obj, prop);
% set initial value if present
obj.(prop) = init_val;
% define property accessor methods
% NOTE: this has to be a simple function_handle (@fun), not
% an anonymous function (@()..) to avoid infinite recursion
p.GetMethod = @get_method;
p.SetMethod = @set_method;
% nested getter/setter functions with closure
function set_method(obj, val)
if isReadOnly
ME = MException('MATLAB:class:SetProhibited', sprintf(...
'You cannot set the read-only property ''%s'' of %s', ...
prop, class(obj)));
throwAsCaller(ME);
end
obj.(prop) = val;
end
function val = get_method(obj)
val = obj.(prop);
end
end
end
end
now in the subclass, instead of defining a dependent property the usual way, we use this new inherited function in the constructor to define a dynamic property:
classdef subklass < klass
%properties (Dependent, SetAccess = private)
% name
%end
%methods
% function val = get.name(obj)
% val = 'Amro';
% end
%end
methods
function obj = subklass()
% call superclass constructor
obj = obj@klass();
% define new properties
add_dyn_prop(obj, 'name', 'Amro');
add_dyn_prop(obj, 'age', [], false)
end
end
end
The output:
>> o = subklass
o =
subklass with properties:
age: []
name: 'Amro'
>> o.age = 10
o =
subklass with properties:
age: 10
name: 'Amro'
>> o.name = 'xxx'
You cannot set the read-only property 'name' of subklass.
Of course now you can customize the getter method based on the property name as you initially intended.
EDIT:
Based on the comments, please find below a slight variation of the same technique discussed above.
The idea is to require the subclass to create a property (defined as abstract in the superclass) containing the names of the desired dynamic properties to be created. The constructor of the superclass would then create the specified dynamic properties, setting their accessor methods to generic functions (which could customize their behavior based on the property name as you requested). I am reusing the same `add_dyn_prop` function I mentioned before.
In the subclass, we are simply required to implement the inherited abstract `dynamic_props` property, initialized with a list of names (or `{}` if you dont want to create any dynamic property). For example we write:
classdef subklass < klass
properties (Access = protected)
dynamic_props = {'name', 'age'}
end
methods
function obj = subklass()
obj = obj@klass();
end
end
end
The superclass is similar to what we had before before, only now is it its responsibility to call the `add_dyn_prop` in its constructor for each of the property names:
classdef klass < dynamicprops % ConstructOnLoad
properties (Abstract, Access = protected)
dynamic_props
end
methods
function obj = klass()
assert(iscellstr(obj.dynamic_props), ...
'"dynamic_props" must be a cell array of strings.');
for i=1:numel(obj.dynamic_props)
obj.add_dyn_prop(obj.dynamic_props{i}, [], false);
end
end
end
methods (Access = private)
function add_dyn_prop(obj, prop, init_val, isReadOnly)
% input arguments
narginchk(2,4);
if nargin < 3, init_val = []; end
if nargin < 4, isReadOnly = true; end
% create dynamic property
p = addprop(obj, prop);
%p.Transient = true;
% set initial value if present
obj.(prop) = init_val;
% define property accessor methods
p.GetMethod = @get_method;
p.SetMethod = @set_method;
% nested getter/setter functions with closure
function set_method(obj,val)
if isReadOnly
ME = MException('MATLAB:class:SetProhibited', sprintf(...
'You cannot set the read-only property ''%s'' of %s', ...
prop, class(obj)));
throwAsCaller(ME);
end
obj.(prop) = val;
end
function val = get_method(obj)
val = obj.(prop);
end
end
end
end
Note: I did not use `ConstructOnLoad` class attribute or `Transient` property attribute, as I am still not sure how they would affect loading the object from a saved MAT-file in regards to dynamic properties.
>> o = subklass
o =
subklass with properties:
age: []
name: []
>> o.name = 'Amro'; o.age = 99
o =
subklass with properties:
age: 99
name: 'Amro'
Problem
In Matlab, I can define a class as such: ``` classdef klass < handle properties(Dependent) prop end end ``` Matlab is perfectly happy instantiating an object of this class, even without defining a getter for `prop`. It only fails when I try to access it (understandably). I'd like to set the `GetMethod` dynamically based upon the property's name. Unfortunately, even when the property is Dependent, the `meta.property` field for `GetMethod` is still read-only. And while inheriting from `dynamicprops` could allow adding a property and programmatically setting its GetMethod in every instance, I don't believe it could be used to change an existing property. I may have to go this route, but as `prop` must exist for every object I'd prefer to simply set the getter on a class-by-class basis. Is such a thing possible? An alternative solution could be through some sort of catch-all method. In other languages, this could be accomplished through a Ruby-like `method_missing` or a PHP-like `__get()`. But as far as I know there's no (documented or otherwise) analog in Matlab. (My use case: this class gets inherited by many user-defined subclasses, and all their dependent properties are accessed in a similar way, only changing based on the property name. Instead of asking users to write `get.*` methods wrapping a call to the common code for each and every one of their dependent properties, I'd like to set them all dynamically with anonymous function pointers containing the necessary metadata).