Using Matlab R2012a, I have the following class hierarchy:
classdef Parent < handle
properties (Abstract, SetAccess = protected)
Limit
end
end
classdef SimpleChild < Parent
properties (SetAccess = protected)
Limit = 1.0
end
end
classdef ExtendedChild < Parent
properties (Access = private)
Child = SimpleChild
end
properties (Dependent, SetAccess = protected)
Limit
end
methods
function this = ExtendedChild
this.Limit = 2;
end
function output = get.Limit(this)
output = this.Child.Limit;
end
function set.Limit(this,input)
this.Child.Limit = input;
end
end
end
This a simple example where the "Parent" class defines an abstract "Limit" property, which is implemented in both the "SimpleChild" and the "ExtendedChild" class. The "ExtendedChild" class encapsulates a private instance on the "SimpleChild" class and forward the access methods (get/set) to the private instance. Constructing an "ExtendedChild" instance fails with the following message:
>> obj = ExtendedChild
Setting the 'Limit' property of the 'SimpleChild' class is not allowed.
Error in ExtendedChild/set.Limit (line 16)
this.Child.Limit = input;
Error in ExtendedChild (line 10)
this.Limit = 2;
I would have expected the "Limit" property to be settable since it is defined in the "Parent" class with a protected SetAccess. I can make the problem disappear if the property is implemented directly in the "Parent" class, but then I cannot redefine it as dependent in the "ExtendedChild" class, which is the point of the construction (separation of interface and implementation).
Can someone tell me if I'm doing something wrong?
Since the Limit property of SimpleChild is protected, you can only set its value from SimpleChild or a subclass of SimpleChild, which is not the case for ExtendedChild.
I'm not entirely sure what you're trying to achieve, so can't really advise on what the "best" way to do it might be. But I would guess that whatever you want, it's unlikely that having a set method for a Dependent property is the right way to achieve it - there are only very rare reasons why you might want to do that.
Related
Lets have a following three classes:
% Main class
classdef MjrClass < handle
properties (SetAccess = immutable, GetAccess = public)
cls;
end
methods (Access = public)
function self = MjrClass()
self.cls = SubClass(2);
end
end
end
% Abstract class
classdef (Abstract) AbsClass < handle
properties (SetAccess = immutable, GetAccess = protected)
p;
end
methods (Access = {?MjrClass})
function self = AbsClass(parent)
self.p = parent;
self.do();
end
end
methods (Access = public)
function show(self)
fprintf('self.p: %d\nself.c: %d\n', self.p, self.c)
end
end
end
% Sub-Class
classdef SubClass < AbsClass
properties (SetAccess = private, GetAccess = public)
c;
end
methods (Access = private)
function do(self)
self.c = self.p*2;
end
end
end
The logic is following. Main class MjrClass defines interface for end user. Inside, it creates (in general) several instances of SubClass class. Since those share some properties and methods they all inherit from AbsClass class. And because the AbsClass class alone has no purpose, it is defined as abstract class.
However as it is, Matlab complains it "Cannot access method 'AbsClass' in class 'AbsClass'". The problem is obviously the Access property of constructor method. But if I leave it public it does not reflect the fact that it shoould be instantiated only within MjrClass class.
The same problem applies to do() method of SubClass class, where Access = private should restrict the method to be called only once within the AbsClass constructor, not allowing user to call it.
The access specifier for the AbsClass constructor doesn't actually allow AbsClass to call that method. So, the fix is simple, allow AbsClass (and its subclasses) to call its own constructor, like this:
methods (Access = {?MjrClass, ?AbsClass})
function obj = AbsClass(...)
....
I'm brand new to OOP in Matlab, and still fairly green when it comes to OOP in general, but what I do know I learnt in C++.
I'm following the Matlab documentation found here Property class and size validation. I want to validate a property so that it must be a specific class and I'm using the example from the link. This is what my class looks like:
classdef simpoint
...
properties
...
outputType dataType
...
end
...
end
In my code dataType is a class I've written. What's more it's abstract.
I'm getting the error
Error defining property 'outputType' of class 'simpoint':
Class dataType is abstract. Specify a default value for property outputType.
The class dataType is abstract to force the user to implement some methods. I'm trying to use property validation to make sure when outputType is set, the class is a subclass of dataType.
I don't really want to set a default value, because forgetting to set outputType should throw an error.
How can I validate outputType to make sure it is a subclass of dataType? Is there a better way to do this in Matlab?
There is a more elegant solution to this problem, which is apparently not well known.
MATLAB has a concept of Heterogeneous Class Hierarchies. This is just fancy way of explicitly declaring the common root class (abstract or not) so that it can be used for property validation. In practice, all you need to do is to make your abstract class inherit from matlab.mixin.Heterogeneous.
Here is a quick example:
classdef (Abstract) AbstractItem < handle & matlab.mixin.Heterogeneous
end
classdef Collection < handle
properties
items AbstractItem
end
end
Then you have no problem:
>> x = Collection
x =
Collection with properties:
items: [0×0 AbstractItem]
Without the matlab.mixin.Heterogeneous inheritance you would get an error like you described:
Error defining property 'items' of class 'Collection'. Class AbstractItem is abstract. Specify a default value for property items.
Your current code uses the following logic:
Create a new simpoint object
Ah this object needs an outputType property
Initialise the outputType property to be an empty dataType object
Uhoh, we can't instantiate an abstract object - error.
Instead, you could also use setters and getters to validate data types. This removes steps 3 and 4 above, since the initial property value will be [].
classdef simpoint < matlab.mixin.SetGet
properties
outputType
end
methods
% ...
end
methods % Setters and getters
function set.outputType( obj, v )
% When the 'obj.outputType = X' is called, this function is
% triggered. We can validate the input first
assert( isa( v, 'dataType' ) );
% If the assertion didn't error, we can set the property
obj.outputType = v;
end
function v = get.outputType( obj )
% Nothing bespoke in the getter (no not strictly needed), just return the value
v = obj.outputType;
end
end
end
For more informative validation, you could use validateattributes instead of assert.
In this case, the default value of outputType will be [] unless you initialise it in the constructor.
Note, by using matlab.mixin.SetGet to enable setters and getters, I've implicitly made your object a handle. In broader OOP terms, the object is now accessed "by reference" rather than "by value". Read more here.
If you don't want a handle then you can remove the < matlab.mixin.SetGet and, by your own comment, define the setter more explicitly
function obj = set.outputType( obj, v )
% Have to return 'obj' if the class isn't a handle.
% ...
end
In MATLAB, one of the attributes of a class (defined after classdef) is Sealed, which means that no class can use it as a superclass (or to be more precise, "to indicate that these classes have not been designed to support subclasses."1).
For example, if I try to instantiate a class that's defined as below (considering table is Sealed):
classdef SomeLie < table
end
I would get the 'MATLAB:class:sealed' error:
>> A = SomeLie;
Error using SomeLie
Class 'table' is Sealed and may not be used as a superclass.
As I refuse to be told by a machine what I may or may not do, I would like to subclass a Sealed class, regardless. How can I do that in MATLAB R2017a?
I'm having a hard time believing that this system is completely airtight, so I'm looking for a solution that would cause the Sealed attribute to be silently ignored (or something of that sort). The desired solution should work without modifying any "library class definitions" to remove Sealed from them.
I tried playing around with "reflection", but arrived at a dead end...
classdef SomeLie % < table
properties (Access = private)
innerTable table;
end
properties (GetAccess = public)
methodHandles struct = struct();
end
methods
function slObj = SomeLie(varargin)
slObj.innerTable = table(varargin{:});
% methodHandles = methods(slObj.innerTable);
ml = ?table; ml = {ml.MethodList.Name}.';
ml = setdiff(ml,'end');
tmpStruct = struct;
for indM = 1:numel(ml)
tmpStruct.(ml{indM}) = str2func([...
'#(varargin)' ml{indM} '(slObj.innerTable,varargin{:})']);
end
slObj.methodHandles = tmpStruct;
end
function varargout = subsref(slObj,varargin)
S = struct(slObj);
varargout{:} = S.methodHandles.(varargin{1}.subs)(varargin{:});
end
end
end
(There's no need to fix the above code, I was just sharing)
I do not think the machine is the problem, but the class designer and he certainly has good motivations to seal the class. "Philosophy" of coding, a part, you could 'own' the class in a wrapper class without defining it sealed.
For example, supposer the class Hello is sealed and has a method (or function, if you wish) sayHello which you would like to use in inherited classes you could define a class FreeHello (public) which contains an instance of Hello. At the constructor you build the corresponding Hello and then you define a sayHello method whose body simply calls your Hello instance and makes it execute the sayHello method (and returns the output, accordingly).
In order to 'open' the sealed class, you need to do these for all properties and public methods; of course you are still not capable of accessing private methods, but now you can subclass your wrapper class, as you wish.
The property postSet of MATLAB handle classes are very handy, however I would be happy to be able to trigger nested classes separately. A minimal example with two nested classes for illustration:
classdef parentClass < handle
properties (SetObservable = true)
childClass
end
methods
function this = parentClass()
this.childClass = childClass();
end
end
end
and
classdef childClass < handle
properties (SetObservable = true)
value
end
methods
function this = childClass()
this.value = 0;
end
end
end
In the example script "runTest"
p = parentClass();
addlistener(p.childClass,'value','PostSet',#(o,e)disp('child value set'));
addlistener(p,'childClass','PostSet',#(o,e)disp('parent value set'));
p.childClass.value = 1;
The result is (as expected)
>> runTest
child value set
However, I am looking for an elegant way to detect the property change on both levels such that the result would be:
>> runTest
child value set
parent value set
One way to do this is to use the fact that the PostSet event gets triggered by default even when the previous and current values are the same. In the constructor of parentClass, we add a listener to the childClass's PostSet event, and the event handler only reassigns the childClass object:
classdef parentClass < handle
properties (SetObservable = true)
childClass
end
methods
function this = parentClass()
this.childClass = childClass();
addlistener(this.childClass,'value','PostSet', #this.handlePropEvent);
end
function handlePropEvent(this, src, event)
this.childClass = this.childClass;
end
end
end
Note that you would want to be more careful implementing this so that the event listener gets adequately disposed of (and reassigned) if the childClass property is assigned a different object.
In practice, childClass should probably implement its own event type which gets triggered by all property changes you're interested in, and parentClass only listens to that event.
I'm trying to change a property in a class called houses via a utility method which is Static. I'm getting terribly confused with the reference obj as I don't know when and where it should be used. I am trying to bypass the constructor method so I can access the setProperty method, but I am getting errors such as too many output arguments. I've tried passing in obj as well as x, but I get similar errors. However, I can change the property a if I pass in a value to the constructor method.
classdef houses
properties
a;
end
methods
% constructor method
function obj = houses()
end
end
methods (Static)
function setProperty(x)
obj.a = x;
end
end
end
In general, you should not use static methods to set properties of a class. If your property is public, then you can use a static method but it is highly recommended that you do not. If your property is private/protected, then you definitely cannot use a static method to modify it.
Your class should look like this then (I took the liberty of stating explicitly the access properties of each block):
classdef houses
properties (Access = private)
a;
end
methods (Access = public)
% constructor method
function obj = houses()
end
function SetA(obj, a)
obj.a = a;
end
function DoSomething(obj, more_parameters)
% Lengthy stuff here
end
end
end
Now, regarding your question about obj: the answer is you must pass obj as the first argument of every instance method. The variable obj refers to the current instance of the class in a generic way. See for example the method DoSomething.
Static methods do not have access to any of the properties of the class, unless public. As such, when declaring a static method, you should not pass the obj variable.
Last thing: always use explicit access modifiers for your properties and methods. It will save you some headaches.
A static method is not typically supposed to access an object (hence it does not have access to obj).
If you want to modify a static propperty (shared by all objects, and the class itself), you can use something like:
classdef houses
properties (Static)
a;
end
methods
% constructor method
function obj = houses()
end
end
methods (Static)
function setProperty(x)
houses.a = x;
end
end
end
Regarding obj, it is the 1st argument of every methods (non static). So when you do:
o = myClass();
o.myMethod(args);
Matlab will see this as:
myMethod(o, args);
So when you define the method, you have to put obj as the 1st argument (in fact you can choose any name, it does not have to be obj).