【问题标题】:Dynamically assign the getter for a dependent property in MATLAB在 MATLAB 中为依赖属性动态分配 getter
【发布时间】:2023-03-26 13:44:01
【问题描述】:

在 Matlab 中,我可以这样定义一个类:

classdef klass < handle
    properties(Dependent)
        prop
    end
end

Matlab 非常乐意实例化此类的对象,即使没有为 prop 定义 getter。只有当我尝试访问它时它才会失败(可以理解)。我想根据属性名称动态设置GetMethod

不幸的是,即使属性是 Dependent,GetMethodmeta.property 字段仍然是只读的。虽然从dynamicprops 继承可以允许添加 一个属性并在每个实例中以编程方式设置它的GetMethod,但我认为它不能用于更改现有属性。我可能不得不走这条路,但由于每个对象都存在prop 必须,我更愿意简单地逐个类地设置getter。这样的事情可能吗?

另一种解决方案可能是通过某种包罗万象的方法。在其他语言中,这可以通过类似 Ruby 的 method_missing 或类似 PHP 的 __get() 来完成。但据我所知,Matlab 中没有(记录或其他)模拟。


(我的用例:这个类被许多用户定义的子类继承,并且它们的所有依赖属性都以类似的方式访问,只是根据属性名称进行更改。而不是要求用户编写get.*方法包装调用它们的每一个依赖属性的公共代码,我想使用包含必要元数据的匿名函数指针来动态设置它们。

【问题讨论】:

    标签: matlab oop dynamic-properties


    【解决方案1】:

    这是我的建议:在超类中创建一个名为add_dyn_prop 的方法。该方法将在子类中调用,而不是像通常那样创建dependent property

    想法是超类从dynamicprops继承并使用addpropadd一个新属性,并根据其名称手动设置其访问器方法。

    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
    

    现在在子类中,我们不再像通常那样定义依赖属性,而是在构造函数中使用这个新继承的函数来定义动态属性:

    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
    

    输出:

    >> 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. 
    

    当然,现在您可以按照最初的意图根据属性名称自定义 getter 方法。


    编辑:

    根据 cmets,请在下面找到上述相同技术的细微变化。

    这个想法是要求子类创建一个属性(在超类中定义为抽象),其中包含要创建的所需动态属性的名称。然后,超类的构造函数将创建指定的动态属性,将它们的访问器方法设置为通用函数(可以根据您的要求根据属性名称自定义它们的行为)。我正在重用我之前提到的 add_dyn_prop 函数。

    在子类中,我们只需要实现继承的抽象dynamic_props 属性,用名称列表初始化(或者{},如果您不想创建任何动态属性)。比如我们写:

    classdef subklass < klass
        properties (Access = protected)
            dynamic_props = {'name', 'age'}
        end
    
        methods
            function obj = subklass()
                obj = obj@klass();
            end
        end
    end
    

    超类与我们之前的类似,只是现在它负责在其构造函数中为每个属性名称调用add_dyn_prop

    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
    

    注意:我没有使用 ConstructOnLoad 类属性或 Transient 属性属性,因为我仍然不确定它们会如何影响从保存的 MAT 文件加载对象的动态属性。

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

    【讨论】:

    • 这与我最终所做的最接近:我让每个子类定义一个属性dynamic_props,并且超类(强制为[ConstructOnLoad](http://www.mathworks.com/help/matlab/matlab_oop/class-constructor-methods.html))在其构造函数中将它们全部初始化,将 GetMethod 设置为一个匿名函数,它保留属性名称以允许通用实现。我认为对用户来说更简单、更明确。我只是希望 Matlab 有更多的动态 OO 编程能力。干杯!
    • @MattB.:我根据您的描述发布了一个替代实现。
    • 是的,我就是这样做的,除了 Transient = true 和constructOnLoad。出于我的目的,它似乎可以很好地保存和加载。我考虑回答我自己的问题,但认为你的问题已经足够接近了。现在真的是!
    【解决方案2】:

    检查这是否是您想要的。问题是用户需要使用 () 来获取属性,这可能很无聊,但无论如何,我认为这样可以更改变量。您不能直接在类上更改它们,但您可以根据需要更改对象属性值。它不需要更改构造函数上的值,您可以使用将由类继承的另一个函数来做到这一点。

    类1.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
    

    输出:

    a = klass2
    b=a.prop_child_1()
    
    
    b =
    
    newGetChild1PropFcn
    

    【讨论】:

    • 是的,这与我在上一个(用例)段落中提到的类似。这适用于一个属性,但对于每个子类中的每个属性来说,它都有很多样板。
    • @Matt B. 你不能为每个子类编写一个抽象方法,将所有依赖属性更改为定义的 fcnHandle 吗?
    • @Matt B. 还是您正试图避免这样做?
    • 当然,但是我需要get.prop1get.prop2get.prop3 等的函数,所有这些都只是调用该函数句柄。如果用户想要将新的依赖属性添加到他们的自定义类之一(扩展这个超类),他们还需要编写这些get.* 包装函数(所有这些都适用于set.*)。我想使用自省(使用metaclass)来查找所有相关属性并一举以编程方式设置getter。而且,如果可能的话,所有这些都可以在一个超类中完成一次。
    • 很有创意。 +1。但我担心要求函数使用可能对我来说有点太大了,无法跳过。我正在重构当前使用subsref 重载以实现上述目标的大型代码库中的核心类。但是自定义 subsref 方法的 difficultiesbugslimitations 一直是大量使用的噩梦,所以我正在寻找更清洁的替代方案。
    猜你喜欢
    • 2012-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-09
    • 1970-01-01
    • 2013-01-19
    • 1970-01-01
    相关资源
    最近更新 更多