Overriding subsref and subsasgn - effect on private properties
14 Ansichten (letzte 30 Tage)
Ältere Kommentare anzeigen
David Young
am 24 Sep. 2011
Kommentiert: David Young
am 13 Okt. 2021
I'm overriding subsref and subsasgn for a class. I want to influence the behaviour of obj(...), but I can't find a good way to do this without also either breaking obj.name property access, or breaking the privacy attributes for the properties.
Examples in the documentation (see "A Class with Modified Indexing") suggest that I check the type of indexing within my subsref/subsasgn functions and call the builtin subsref/subsasgn if the type is '.'. The problem is that because these are called from a class method, the access protection on properties is overriden - so I can access and update private properties from outside the class, as if the protection was not there.
Here's an example class
classdef test
properties (Access = private)
x = 'ought to be private'
end
methods
function v = subsref(M, S)
switch S.type
case '()'
v = 'this is ok';
case '{}'
error('{} indexing not supported');
case '.'
v = builtin('subsref', M, S); % as per documentation
end
end
end
end
and here's what goes wrong when I use it:
>> t = test
t =
test with no properties.
Methods
>> t(1)
ans =
this is ok
>> t.x
ans =
ought to be private
The attempt to access t.x should not succeed.
One solution I can think of is to write set.x and get.x methods for every single private property, to reimplement the protection that the Access attribute ought to provide.
[EDIT - added 16 hours after original post] Another possible solution: write code to analyse the subscript argument, consulting meta.property, before calling the builtin subsref/subsasgn. Not that hard, but it's ugly and probably inefficient to reimplement existing functionality.
Does anyone know a better way?
0 Kommentare
Akzeptierte Antwort
James Lebak
am 13 Okt. 2021
Beginning in MATLAB R2021b it is possible to overload parentheses, dot, and brace indexing independently. This gives the ability to overload paren-indexing while preserving the default behavior and performance of dot-indexing for the class.
Weitere Antworten (4)
David Young
am 3 Okt. 2011
4 Kommentare
Steven Lord
am 13 Okt. 2021
@James Lebak In order to increase the visiblity of this comment, please turn it into its own answer.
Daniel Shub
am 25 Sep. 2011
Apologies for the long answer (that might not be helpful or even an answer). The answer is so long since I am not sure what I am doing is anywhere near optimal and would love some feedback or to see what others are doing. I find the whole subs* access permissions to be extremely difficult and poorly documented by TMW. I don't know enough about other languages and OOP to know if is MATLAB specific or not. My solution is based upon using a metaclass object to determine the permissions (similar to what you hint at in your edit). The key to this solution is I limit my overloaded subs* methods to accessing only public properties and methods. I am comfortable with this since methods call the builtin subs* methods by default and you need to specifically code a call to the overloaded subs* methods. If you want your methods to be able to use the overloaded subs* methods to access private/protected properties and methods, you have two options. The first way is to extend the subs* methods to determine the access permissions of the caller. You can probably do this with dbstack and metaclass to figure out if the calling function has access to private and/or protected properties and methods. The second way is to write privatesubs* and protectedsubs* methods that have access permissions of private and protected, respectively. If a method has permission to access the protectedsubs* methods, then it also should have permission to access all protected properties and methods. Similarly, if a method has permission to access the privatesubs* methods, then it also should have permission to access all private and protected properties and methods. The first solution is easier for developers of subclasses since they only have to concern themselves with the subs* methods. I find the second easier to implement since I do not have to worry about determining the access permissions of the caller. Below is some slightly edited code for my actual implementation of the subsref method.
I start with defining a subsref method for my TopMost class. The TopMost class is not a child class of any other classes, but it is handle compatible (although I don't think that matters).
function varargout = subsref(Obj, S)
% Overloaded subsref
%
% varargout = subsref(Obj, S)
%
% This function overloads the builtin subsref for the TopMost class. It only allows access to
% public properties and methods. Access to private and protected methods is
% denied even if subsref is called from another method of the class. If you
% need to access a private or protected method via a subsref type call, you
% must implement your own method.
% Validate the number of arguments.
nRequiredArguments = 2;
nOptionalArguments = 0;
error(nargchk(nRequiredArguments, nRequiredArguments+nOptionalArguments, nargin, 'struct'));
% Overload the subsref method.
subsrefcheck(Obj, S);
[varargout{1:nargout}] = builtin('subsref', Obj, S);
end
The only thing this method does is check if the substruct object is "valid" (see the subsrefcheck function further below). The method hands everything off to the builtin subsref. The reason for this method is if you have the class hierarchy SubClass < ParentClass < TopMost, and SubClass overloads the subsref method, then I want the SubClass subsref method to use the ParentClass subsref method, and not the builtin subsref method, as the default. The problem is that MATLAB throws an error if the ParentClass does not have a subsref method (even though the ParentClass could use the builtin subsref method). By adding a subsref method to TopMost, I assure myself that ParentClass has a subsref method.
function varargout = subsref(Obj, S)
% Overloaded subsref
%
% varargout = subsref(Obj, S)
%
% This function overloads the ParentClass subsref method (which might be defined by the TopMost class). It only allows
% access to public properties and methods. Access to private and protected
% methods is denied even if subsref is called from another method of the
% class. If you need to access a private or protected method via a subsref
% type call, you must implement your own method.
% Validate the number of arguments.
nRequiredArguments = 2;
nOptionalArguments = 0;
error(nargchk(nRequiredArguments, nRequiredArguments+nOptionalArguments, nargin, 'struct'));
% Validate the arguments.
subsrefcheck(Obj, S);
% Overload the subsref method.
if strcmp(S(1).type, '.') && strcmp(S(1).subs, 'MyProp')
Value = Obj.MyPropSubsRefGet(S);
varargout = {Value};
else
[varargout{1:nargout}] = subsref@ParentClass(Obj, S); % This might actually jump all the way to subsref@Topmost(Obj, S);
end
end
Here the overloaded subsref method calls a special "get" method (MyPropSubsRefGet) for the property MyProp and passes all the other cases on to ParentClass.
Below is my subsrefcheck and the functions it depends on. I use these function in many of my classes, so I do not make them a method of my TopMost class, although I could.
function subsrefcheck(Obj, S)
% subsrefcheck checks if the substruct is valid for subsref
%
% subsrefcheck(Obj, S)
%
% Checks if the substruct S is valid for use with subsref on the object Obj.
% The substruct is not valid for the Obj if the substruct is not valid (see
% validatesubstruct). Further, if S accesses a property, the substruct is
% not valid if the get access of the property is not public. Finally, if S
% accesses a method, the substruct is not valid if the access of the method
% is not public. The function returns nothing if S is valid and throws the
% approariate error if S is not valid.
% Validate the number of arguments.
nRequiredArguments = 2;
nOptionalArguments = 0;
error(nargchk(nRequiredArguments, nRequiredArguments+nOptionalArguments, nargin, 'struct'));
% Validate the arguments.
validatesubstruct(S);
% Parse the substruct
if length(S) >= 1 && strcmp(S(1).type, '.')
SubsNameString = S(1).subs;
SubObj = Obj;
elseif length(S) >= 2 && strcmp(S(1).type, '()') && strcmp(S(2).type, '.')
SubsNameString = S(2).subs;
SubObj = Obj(S(1).subs{:});
else
return;
end
% Check if the property/method is public.
switch lower(gettype(Obj, SubsNameString))
case 'field'
case 'property'
[GetAccessString, SetAccessString] = getpropertyaccess(SubObj, SubsNameString); %#ok<NASGU>
if ~strcmp(GetAccessString, 'public')
throwAsCaller(MException('MATLAB:class:GetProhibited', ...
['Getting the ''', SubsNameString, ''' property of the ''', class(Obj), ''' class is not allowed.']));
end
case 'method'
AccessString = getmethodaccess(SubObj, SubsNameString);
if ~strcmp(AccessString, 'public')
throwAsCaller(MException( ...
'MATLAB:class:MethodRestricted', ...
['Cannot access method ''', SubsNameString, ''' in class ''', class(Obj), ''.']));
end
otherwise
throwAsCaller(MException('MATLAB:noSuchMethodOrField', ...
[ 'No appropriate method, property, or field ', SubsNameString, ' for class ', class(Obj), '.']));
end
end
function validatesubstruct(S)
% Checks if S is a valid substruct argument
%
% validatesubstruct(S)
%
% Returns nothing if S is a valid substruct (i.e., could have been generated
% by the substruct function) and throws the appropriate error if S is not a
% valid substruct.
% Validate the number of arguments.
nRequiredArguments = 1;
nOptionalArguments = 0;
error(nargchk(nRequiredArguments, nRequiredArguments+nOptionalArguments, nargin, 'struct'));
% Validate the arguments.
assert(isstruct(S), 'MATLAB:subsArgNotStruc', ...
'Subscript argument to SUBSREF and SUBSASGN must be a structure.');
assert(length(fieldnames(S)) == 2, 'MATLAB:subsMustHaveTwo', ...
'Subscript argument to SUBSREF and SUBSASGN must have two fields.');
assert(isequal(sort(fieldnames(S)), sort({'subs'; 'type'})), ...
'MATLAB:subsMustHaveTypeSubs', ['Subscript argument to SUBSREF ', ...
'and SUBSASGN must have two fields whose names are "type" ', ...
'and "subs".']);
assert(~isempty(S), 'MATLAB:subsArgEmpty', ...
'Subscript argument to SUBSREF and SUBSASGN must not be empty.');
assert(all(cellfun(@(x)(ischar(x) || iscell(x)), {S.subs})), ...
'MATLAB:subsSubsMustBeCellOrChar', ...
['The "subs" field for the subscript argument to SUBSREF ', ...
'and SUBSASGN must be a cell or character array.']);
assert(all(cellfun(@(x)ischar(x), {S.type})), ...
'MATLAB:subsTypeMustBeChar', ...
['The "type" field for the subscript argument to SUBSREF ', ...
'and SUBSASGN ', '\n', 'must be a character array.']);
assert(all(cellfun(@(x)any(strcmp(x, {'.'; '()'; '{}'})), {S.type})), ...
'MATLAB:subsTypeMustBeSquigglyOrSmooth', ...
['The "type" field for the subscript argument to SUBSREF ', ...
'and SUBSASGN ', '\n', 'must be a character array ', ...
'of "." or "{}" or "()".']);
assert(all(cellfun(@(x, y)(~strcmp(x, '.') || ~iscell(y) ...
|| ~isempty(y)), {S.type}, {S.subs})), ...
'MATLAB:subsCellIsEmpty', ...
['The "subs" field for the subscript argument to SUBSREF ', ...
'and SUBSASGN must be a non-empty cell or character array.']);
for iSub = 1:length(S)
assert(~strcmp(S(iSub).type, '()') || iscell(S(iSub).subs), ...
'MATLAB:subsSmoothTypeSubsMustBeCell', ...
'SUBS field must be a cell array for () TYPE.');
assert(~strcmp(S(iSub).type, '{}') || iscell(S(iSub).subs), ...
'MATLAB:subsSquigglyTypeSubsMustBeCell', ...
'SUBS field must be a cell array for {} TYPE.');
assert(~strcmp(S(iSub).type, '()') || (iSub == length(S) || ...
strcmp(S(iSub+1).type, '.')) , ...
'MATLAB:subsDotMustFollow', ...
'Only a dot field name can follow ()''s.');
end
end
function AccessString = getmethodaccess(Obj, MethodNameString)
% Gets the Access attribute of the method
%
% AccessString = getmethodaccess(Obj, MethodNameString)
%
% Uses the metaclass of the object Obj to determine the method
% MethodNameString access permission (private, protected, public) and
% returns the access permission as AccessString.
% Validate the number of arguments.
nRequiredArguments = 2;
nOptionalArguments = 0;
error(nargchk(nRequiredArguments, nRequiredArguments+nOptionalArguments, nargin, 'struct'));
% Validate the arguments.
assert(isobject(Obj), [mfilename, ':ArgumentCheck'], ...
['The Obj of class ''', class(Obj), ''' is not an object of a MATLAB class.']);
validateattributes(MethodNameString, {'char'}, {'row'}, [mfilename, ':ArgumentCheck'], 'MethodNameString');
MetaClassObj = metaclass(Obj);
NameList = {MetaClassObj.MethodList(:).Name};
iMethod = find(strcmp(MethodNameString, NameList), 1, 'first');
assert(~isempty(iMethod), [mfilename, ':ArgumentCheck'], ...
['''', MethodNameString, ''' is not a method of the ''', class(Obj), ''' class.']);
AccessString = MetaClassObj.MethodList(iMethod).Access;
end
function [GetAccessString, SetAccessString] = getpropertyaccess(Obj, PropertyNameString)
% Gets the GetAccess and SetAccess attributes of the property
%
% [GetAccessString, SetAccessString] = getpropertyaccess(Obj,
% PropertyNameString)
%
% Uses the metaclass of the object Obj to determine the property
% PropertyNameString get and set access permissions (private, protected,
% public) and returns the access permissions as GetAccessString,
% SetAccessString.
% Validate the number of arguments.
nRequiredArguments = 2;
nOptionalArguments = 0;
error(nargchk(nRequiredArguments, nRequiredArguments+nOptionalArguments, nargin, 'struct'));
% Validate the arguments.
assert(isobject(Obj), [mfilename, ':ArgumentCheck'], ...
['The Obj of class ''', class(Obj), ''' is not an object of a MATLAB class.']);
validateattributes(PropertyNameString, {'char'}, {'row'}, [mfilename, ':ArgumentCheck'], 'PropertyNameString');
MetaClassObj = metaclass(Obj);
NameList = {MetaClassObj.PropertyList(:).Name};
iProperty = find(strcmp(PropertyNameString, NameList), 1, 'first');
assert(~isempty(iProperty), [mfilename, ':ArgumentCheck'], ...
['''', PropertyNameString, ''' is not a property of the ''', class(Obj), ''' class.']);
GetAccessString = MetaClassObj.PropertyList(iProperty).GetAccess;
SetAccessString = MetaClassObj.PropertyList(iProperty).SetAccess;
end
I created the validate substruct function by trial and error. Basically I tried the builtin subsref function with every possible combination of arguments I could think of and recorded the errors. I really wish the substruct was a class. I might be being to restrictive on my substruct and reducing the power of subsref, but it is not causing me any problems. Also note that in newer versions of MATLAB the getmethodaccess and getpropertyaccess might be able to use isprop and ismethod. I am not sure what the performance implications of that change would be.
3 Kommentare
Daniel Shub
am 28 Sep. 2011
I am glad it was helpful. I think this is one case where the closed source nature of MATLAB is problematic. I would love to see how they implemented subsref. Also, there are so few classes in MATLAB that are really built in the MATLAB OO structure that there is basically only the pathetically simple examples to go off of.
I sometimes wonder how many people use the MATLAB OO framework.
Malcolm Lidierth
am 3 Okt. 2011
David
Publishing private properties from public methods is not unique to MATLAB. It can be source of problems in other OOP languages too - Java for one. You seem to be asking for the MATLAB-supplied generic public subsref to recognise a "superprivate" property and refuse access to it. As you state, you implement that yourself by customising subsref. A single switch block dealing with '.' would do:
switch propname
case {...private properties list...}
throw(....)
otherwise
...
end
On a related issue: Should a single subsref deal with '.', '()' and '{}'. Yes is my vote. The conditional code has to be executed somewhere - whether by MATLAB or in the user-supplied subsref. If the user has control they can control the sequence e.g. deal with '()' first in a switch block if that is the most common/speed-critical case. Likewise, in the switch block above, deal with the public properties first if they are accessed more often - which seems likely.
8 Kommentare
Malcolm Lidierth
am 5 Okt. 2011
@David
Or a Python developer perhaps:
"We are all individuals".
"I'm not"
Malcolm Lidierth
am 28 Sep. 2011
David
Would it help to have your test class extend a superclass that has the private properties in it?:
classdef testprivate
properties (Access = private)
x = 'ought to be private'
end
methods(Access=protected)
function x=getX(obj)
x=obj.x;
return
end
end
end
classdef test < testprivate
methods(Access=protected)
function x=getX(obj)
x=getX@testprivate(obj);
return
end
end
methods(Access=public)
function x=BreakTheRules(obj)
x=obj.getX();
return
end
end
end
Then
>> obj=test;
>> obj.x
Getting the 'x' property of the 'testprivate' class is not allowed.
>> obj.getX()
Error using test/getX
Cannot access method 'getX' in class 'test'.
but,
>> obj.BreakTheRules()
ans =
ought to be private
Siehe auch
Kategorien
Mehr zu Class Introspection and Metadata finden Sie in Help Center und File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!