Dynamically assign getter for dependent property in MATLAB

In Matlab, I can define a class as such:

classdef klass < handle properties(Dependent) prop end end 

Matlab does an excellent job of implementing an object of this class, without even defining a getter for prop . This fails when I try to access it (understandably). I would like to dynamically set GetMethod based on the property name.

Unfortunately, even if the Dependent property, the meta.property field for GetMethod remains read-only. Although inheriting from dynamicprops may allow you to add a property and programmatically set it to GetMethod in each instance, I do not believe that it can be used to modify an existing property. I may have to go this route, but since prop must exist for every object, I would rather just set the getter to class-by-class. Is it possible?

An alternative solution could be some catch-all method. In other languages, this can be accomplished using Ruby-like method_missing or PHP-like __get() . But as far as I know, there is no analogue in Matlab (documented or otherwise).


(My use: this class is inherited by many custom subclasses, and all their dependent properties are accessible in a similar way, only changing based on the name of the property. Instead of asking users to write get.* methods get.* Wrapping a call to the common code for each of their dependent properties, I I would like to install them dynamically using anonymous function pointers containing the necessary metadata).

+5
source share
2 answers

Here is my suggestion: create a method in the superclass called add_dyn_prop . This method should be called in subclasses instead of the usual dependent property .

The idea is that the superclass inherits from dynamicprops and uses addprop to add a new property and set its access 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 the dependent property in the usual way, we use this new inherited function in the constructor to define the 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 

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 configure the getter method based on the name of the property that you originally planned.


EDIT:

Based on the comments, you may find a slight change to the same method as discussed above.

The idea is to require the subclass to create a property (defined as abstract in the superclass) containing the names of the required dynamic properties to be created. The superclass constructor will then create the specified dynamic properties by setting their access methods to universal functions (which could customize their behavior based on the name of the property as you wish). I am reusing the same add_dyn_prop function that I mentioned earlier.

In the subclass, we just need to implement the inherited abstract property dynamic_props , initialized with a list of names (or {} if you don't want to create a 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 it was before, only now its obligation to call 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 have not used the ConstructOnLoad class attribute or Transient , since I'm still not sure how they will affect the loading of an object from a saved MAT file regarding dynamic properties.

 >> o = subklass o = subklass with properties: age: [] name: [] >> o.name = 'Amro'; o.age = 99 o = subklass with properties: age: 99 name: 'Amro' 
+4
source

Make sure that this is what you want. The problem is that the user will need to get using () properties, which can be quite boring, but in any case, I think you can change the variables. You cannot change them directly in the class, but you can change the properties of objects on demand. There is no need to change the values ​​in the constructor, you can do this using another function that will be inherited by the classes.

klass1.m

 classdef(InferiorClasses = {?klass2}) klass < handle methods function self = klass selfMeta = metaclass(self); names = {selfMeta.PropertyList.Name}; for name = names switch name{1} case 'prop_child_1' self.(name{1}) = @newGetChild1PropFcn; case 'prop_child_2' self.(name{1}) = @newGetChild2PropFcn; end end end end methods(Static) function out = prop out = @defaultGetPropFcn; end end end function out = defaultGetPropFcn out = 'defaultGetPropFcn'; end function out = newGetChild1PropFcn out = 'newGetChild1PropFcn'; end function out = newGetChild2PropFcn out = 'newGetChild2PropFcn'; end 

klass2.m

 classdef klass2 < klass properties prop_child_1 = @defaultGetChildPropFcn1 prop_child_2 = @defaultGetChildPropFcn2 end methods function self = klass2 self = self@klass ; end end end function out = defaultGetChildPropFcn1 out = 'defaultGetChildPropFcn1'; end function out = defaultGetChildPropFcn2 out = 'defaultGetChildPropFcn2'; end 

Output:

 a = klass2 b=a.prop_child_1() b = newGetChild1PropFcn 
+2
source

Source: https://habr.com/ru/post/982893/


All Articles