Конструктор подкласса отказывается принимать любые аргументы имя-значение - PullRequest
3 голосов
/ 15 апреля 2020

TL; DR

У меня проблема с функциональностью arguments, из-за которой подкласс не может быть создан, если ему передано любые пары аргументов имя-значение .

(определения классов приведены в конце.)


Функциональность arguments, представленная в R2019b, дает большие перспективы с точки зрения упрощения проверки аргументов и удаление кода шаблона из функций [1] . Однако при попытке реализовать имя-значение (NV) аргументы, взятые из public свойств класса , я получил следующую ошибку:

Invalid argument list. Check for wrong number of positional arguments or placement of positional arguments after 
name-value pairs. Also, check for name-value pairs with invalid names or not specified in pairs. 

перед любым код подкласса даже выполняется. Это сообщение сбивает с толку, потому что завершение табуляции, кажется, работает как ожидается для пар NV:

enter image description here

Более того, если никакие аргументы не передаются вообще, все отлично работает:

>> FluidLayer()
ans = 
  FluidLayer with properties:

    prop1: NaN
    prop2: NaN

против

>> FluidLayer('prop1',1)
Error using FluidLayer
Invalid argument list. ...

Вопросы:

  1. Почему я получаю ошибку? Я не думаю, что я использую механизм arguments каким-либо непреднамеренным или недокументированным способом, поэтому я вдвойне озадачен тем, что могу делать неправильно (если предположить, что это не ошибка).
  2. Что можно сделать, чтобы решить эту проблему, кроме отказа от всего подхода arguments (я хотел бы сохранить предложения с именами аргументов)? Я рассмотрел переход на varargin и / или использование functionSignatures.json подхода - но это требует значительно больше работы.

classdef Layer < handle & matlab.mixin.Heterogeneous

  properties (GetAccess = public, SetAccess = protected)
    prop1(1,1) double {mustBeNonempty} = NaN
    prop2(1,1) double {mustBeNonempty} = NaN
  end % properties

  methods (Access = protected)

    function layerObj = Layer(props)
      % A protected/private constructor means this class cannot be instantiated
      % externally, but only through a subclass.
      arguments
        props.?Layer
      end

      % Copy field contents into object properties
      fn = fieldnames(props);
      for idxF = 1:numel(fn)
        layerObj.(fn{idxF}) = props.(fn{idxF});
      end
    end % constructor
  end % methods

end
classdef FluidLayer < Layer

  properties (GetAccess = public, SetAccess = protected)
    % Subclass-specific properties

  end % properties

  methods 
    function layerObj = FluidLayer(props)
      arguments
        props.?FluidLayer
      end

      % Create superclass:
      propsKV = namedargs2cell(props);
      layerObj = layerObj@Layer(propsKV{:});

      % Custom modifications:

    end % constructor
  end % methods

end

Ответы [ 2 ]

2 голосов
/ 15 апреля 2020

Мне удалось упростить ваш пример до этого:

classdef Layer

    properties (GetAccess = public, SetAccess = protected)
        prop1
        prop2
    end % properties

    methods

        function layerObj = Layer(props)
            arguments
                props.?Layer
            end
            disp(props)
        end % constructor

    end % methods

end

Теперь Layer('prop1',1) выдает ошибку, как вы описываете.

Таким образом, это не имеет ничего общего с подклассами или Наследование.

Однако, если мы удалим ограничение SetAccess = protected (оставив свойства с publi c получить и установить доступ), то все будет работать так, как вы ожидали.

Я не Не знаю, почему ограничение доступа по множеству ограничило бы этот вариант использования, поскольку оно не имеет ничего общего с записью этих свойств, и метод класса должен в любом случае иметь доступ к множеству. Я думаю, что это ошибка.

1 голос
/ 16 апреля 2020

Документация R2020a гласит, что синтаксис structName.?ClassName может использоваться только с " устанавливаемыми свойствами, определенными классом (то есть, всеми свойствами с public SetAccess) ". Таким образом, использование его со свойствами protected явно не поддерживается.

Таким образом, если мы хотим использовать механизм проверки «automati c» arguments, у нас нет выбора, кроме как установить SetAccess до public. Однако это решение подвергает свойства объекта нежелательным внешним изменениям, и поэтому предлагается обходной путь, основанный на нескольких принципах:

  1. properties теперь имеет public SetAccess, как того требует документация .
  2. Добавлены пользовательские установщики, которые выполняют проверку доступа на основе сравнения dbstack и meta.class.

Новый Layer.m (обратите внимание на 2 новых блока methods):

classdef Layer < handle & matlab.mixin.Heterogeneous

  properties (GetAccess = public, SetAccess = public)
    prop1(1,1) double {mustBeNonempty} = NaN
    prop2(1,1) double {mustBeNonempty} = NaN
  end % properties

  %% Constructor
  methods (Access = protected)

    function layerObj = Layer(props)
      % A protected/private constructor means this class cannot be instantiated
      % externally, but only through a subclass.
      arguments
        props.?Layer
      end

      % Copy field contents into object properties
      fn = fieldnames(props);
      for idxF = 1:numel(fn)
        layerObj.(fn{idxF}) = props.(fn{idxF});
      end
    end % constructor

  end % protected methods  

  %% Setters & Getters  
  methods

    function set.prop1(obj, val)
      if Layer.getCallerMetaclass() <= ?Layer
        obj.prop1 = val;
      else
        Layer.throwUnprotectedAccess();
      end
    end

    function set.prop2(obj, val)
      if Layer.getCallerMetaclass() <= ?Layer
        obj.prop2 = val;
      else
        Layer.throwUnprotectedAccess();
      end
    end

  end % no-attribute methods

  %% Pseudo-protected implementation  
  methods (Access = protected, Static = true)

    function throwUnprotectedAccess()
      stack = dbstack(1);
      [~,setterName,~] = fileparts(stack(1).name);      
      throw(MException('Layer:unprotectedPropertyAccess',...
        ['Unable to set "', stack(1).name(numel(setterName)+2:end),...
        '", as it is a protected property!']));
    end    

    function mc = getCallerMetaclass()
      stack = dbstack(2, '-completenames');
      if isempty(stack)
        mc = ?meta.class;
      else
        [~,className,~] = fileparts(stack(1).file);
        mc = meta.class.fromName(className);
      end
    end

  end % protected static methods
end % classdef

Новый FluidLayer.m (добавлен метод foo):

classdef FluidLayer < Layer

  properties (GetAccess = public, SetAccess = protected)
    % Subclass-specific properties

  end % properties

  methods
    %% Constructor
    function layerObj = FluidLayer(props)
      arguments
        props.?Layer
      end

      % Create superclass:
      propsKV = namedargs2cell(props);      
      layerObj = layerObj@Layer(propsKV{:});
    end % constructor

    function obj = foo(obj)
      obj.prop1 = obj.prop1 + 1;
    end

  end % methods
end % classdef

Вот демонстрация того, как он работает:

>> fl = FluidLayer('prop1', 2, 'prop2', 1)
fl = 
  FluidLayer with properties:

    prop1: 2
    prop2: 1

>> fl.prop1 = 5;  % attempting to set property directly (unintended)
Error using Layer/throwUnprotectedAccess (line 51)
Unable to set "prop1", as it is a protected property!
Error in Layer/set.prop1 (line 32)
        Layer.throwUnprotectedAccess(); 

>> fl.foo()       % attempting to set property through method (intended)
ans = 
  FluidLayer with properties:

    prop1: 3
    prop2: 1
>> 

В вывод: можно преодолеть ограничение SetAccess = public, используя методы установки, но требуются запутанные модификации кода класса.

Практические замечания:

  1. Ограничение getCallerMetaclass в что он неправильно идентифицирует пакеты, что приводит к потенциально пустым объектам метакласса. Так что имейте в виду, что эта функция должна быть изменена, если это так.
  2. dbstack вызывается несколько раз, хотя в этом нет необходимости (ее можно вызвать один раз в установщике, а затем можно получить результат
  3. Код установщика для различных свойств имеет длину 5 строк и в основном реплицируется (за исключением имени свойства) - это можно улучшить, переместив имя свойства через dbstack.
...