Dynamically assign the getter for a dependent property in MATLAB

ⅰ亾dé卋堺 提交于 2019-12-03 16:52:17

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'

Check if this is what you want. The problem is that the user will need to get the properties using (), which may be quite boring, but anyway, I think this way you can change the variables. You can't change them directly on the class, but you can change the objects property values on demand. It doesn't need to change the values on the constructor, you can do that 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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!