I have one superclass, class0, which has several subclasses, class1, class2, class3, class4, which are slight variations on class0, with some restrictions on property values, and perhaps an extra property.
For certain objects of class0 I know they actually fulfill the requirements to belong to one of the subclasses, and I want to convert the class of those objects. Right now, it seems that the only way to convert obj0 of class0 into obj1 of class1, is to construct an empty obj1 and then meticulously copy each non-dependent propertyvalue from obj0 to obj1. And this goes for all the subclasses.
In the old system, you simply did
>> obj1 = class(struct(obj0),'class1');
(or perhaps you needed to add an extra field), and off you go. Now, I have tried giving obj0 as input to the class1 constructor--having made sure that all class constructors accept objects as input--e.g.
>> obj1_new = class1(obj1)
is OK. But I cannot find a way to do
>> obj1 = class1(obj0)
without getting the error
??? When constructing an instance of class 'class1', the
constructor must preserve the class of the returned object.
Any idea if this is even possible without going the long route of property-by-property copying, which I find tedious and error-prone?
?yvind
Please post the code for your constructor(s).
--
Doug Schwarz
dmschwarz&ieee,org
Make obvious changes to get real email address.
Superclass constructor:
function obj = scan(varargin)
if ~isempty(varargin),
if isa(varargin{1},'scan'),
obj = varargin{1};
else
set(obj,varargin{:});
obj.Pos = scan.coord_format_check(obj.Pos);
end
end
end
Subclass constructor:
function obj = bpscan(varargin)
obj = obj@scan(varargin{:});
obj.Pos = bpscan.coord_format_check(obj.Pos);
end
Now I get the error:
"??? When constructing an instance of class 'bpscan', the constructor
must preserve the class of the returned object."
One way around this is, as I said, copying each and every property from the superclass object into a subclass object, but that isn't really straightforward, what with dependent properties and so on, and also I have several subclasses, which would all have to include lots of field-by-field copying code.
I seems that in Java, casting from superclass to subclass is trivial, so I thought there would be some straightforward way in Matlab as well.
?yvind
Okay, I see what you mean and I don't see any other way around it
either. Sorry.
You could make a method of scan be a copy operation that would loop
through the properties and copy them automatically. Something like
function obj = copy(obj,superobj)
prop = properties(superobj);
for i = 1:length(prop)
obj.(prop{i}) = superobj.(prop{i});
end
end
then in the bpscan constructor you can test if the input is of class
scan and, if so,
obj = obj.copy(varargin{1});
This is untested, but I think it should work.
N.B.: properties(superobj) will only find unhidden properties. To find
all property names you can use
prop = fieldnames(struct(superobj));
instead.
Both these constructors are in classdef files and the bpscan classdef file
starts with:
classdef bpscan < scan
to indicate that bpscan is a subclass of scan, correct?
> Now I get the error:
> "??? When constructing an instance of class 'bpscan', the constructor
> must preserve the class of the returned object."
This sort of sounds like bpscan doesn't inherit from scan, and so when you
call obj@scan MATLAB thinks you're trying to make the bpscan object into a
scan object.
--
Steve Lord
sl...@mathworks.com
Correct.
> > Now I get the error:
> > "??? When constructing an instance of class 'bpscan', the constructor
> > must preserve the class of the returned object."
>
> This sort of sounds like bpscan doesn't inherit from scan, and so when you
> call obj@scan MATLAB thinks you're trying to make the bpscan object into a
> scan object.
Well it does inherit from scan, or so I believe. Everything has been working fine, properties and methods inherited and so on. It is *only* when I pass a scan (superclass) object into the bpscan (subclass) constructor that things go wrong.
?yvind
Hmm. Maybe. I'll see how it works out. But some properties are dependent, and shouldn't be set at all, and there's all sorts of stuff going on with set-methods and get-methods and whatnot. Anyway, there ought to be some built-in behaviour for this type of casting.
?yvind
After a lot of thought and reading of the docs (and previous posts), it started to make sense. You can't pass a subclass object to a parent class constructor from within the subclass constructor. For instance if you have a Person class and a Man subclass, you can't pass the "in construction" Man object to the Person constructor. The Man object is not fully instantiated until it exits its constructor. This especially makes sense for value classes.
I had a similar situation where each subclass had its own standard value for an attribute. I'm new to Matlab's object conventions, and went through some variations before I came up with a (to me) satisfactory variation. Here's a quick example of what I did, in the order of revisions I went through.
Option 1 I didn't like in my situation, because it caused me to have to explicitly set the gender property in the subclass constructor. With lots of subclasses and properties, it was easy to forget, and was open to having the property be inconsistent with the subclass. Also, it required the subclass author to know to set the gender - no compiler warnings.
Option 1
--------------
classdef Person
properties
gender;
end
end
classdef Man
% set, get methods as appropriate
methods
function obj = Man()
obj.gender = 'male';
end
end
end
Option 2 was an improvement, in that I couldn't get the wrong value for the property for each subclass. Unfortunately, the fallout was that I had to reference gender like this: Man.gender. In other words, I couldn't loop through a list of Person objects, and get their gender. I would have to inspect each object to determine its subclass to appropriately reference its constant.
Option 2
-------------
classdef Person
properties (Abstract, Constant)
gender
end
end
classdef Man
properties(Constant)
gender='male';
end
end
Option 3 fixed the looping issue. It's now possible to loop through a list of Person objects, and be assured there is an accessible gender property. However, it seemed strange to have to redefine the property (gender) for every subclass. The property isn't really different for each subclass, only the value. Again, with lots of properties and subclasses, it was open to coding inconsistencies. And still lots of responsibility on the subclass author to provide the correct gender.
Option 3
------------
classdef Person
properties (Abstract)
gender
end
end
classdef Man
properties (Constant, SetAccess = private, GetAccess = private)
private_gender = 'male';
end
properties (Dependent=true, GetAccess = public)
gender = Man.private_gender;
end
end
I ended up with Option 4. This moves the get method into the parent class, and the only thing required of the subclass author is to provide the protected_gender constant -- and the compiler warns if it is left out. Now only the value changes across subclasses, not the property or the property methods.
Option 4
---------------
classdef Person
properties (Abstract, Constant, GetAccess=protected)
protected_gender;
end
properties(Dependent=true, SetAccess = private)
gender;
end
methods
function value = get.gender(obj)
value = obj.protected_gender;
end
end
end
classdef Man
properties (Constant, GetAccess = protected)
protected_gender = 'male';
end
end
After working through this, I realized it was very close to the Template design pattern. The Parent class is implementing the logic behind providing the gender (the Template), while the subclasses provide the details, i.e., the actual value.
This worked well for me in my situation. Kind of a long rant, but hope it helps someone out there.