Документация R2020a гласит, что синтаксис structName.?ClassName
может использоваться только с " устанавливаемыми свойствами, определенными классом (то есть, всеми свойствами с public
SetAccess
) ". Таким образом, использование его со свойствами protected
явно не поддерживается.
Таким образом, если мы хотим использовать механизм проверки «automati c» arguments
, у нас нет выбора, кроме как установить SetAccess
до public
. Однако это решение подвергает свойства объекта нежелательным внешним изменениям, и поэтому предлагается обходной путь, основанный на нескольких принципах:
properties
теперь имеет public
SetAccess
, как того требует документация . - Добавлены пользовательские установщики, которые выполняют проверку доступа на основе сравнения
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
, используя методы установки, но требуются запутанные модификации кода класса.
Практические замечания:
- Ограничение
getCallerMetaclass
в что он неправильно идентифицирует пакеты, что приводит к потенциально пустым объектам метакласса. Так что имейте в виду, что эта функция должна быть изменена, если это так. dbstack
вызывается несколько раз, хотя в этом нет необходимости (ее можно вызвать один раз в установщике, а затем можно получить результат - Код установщика для различных свойств имеет длину 5 строк и в основном реплицируется (за исключением имени свойства) - это можно улучшить, переместив имя свойства через
dbstack
.