Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Changing class of object

45 views
Skip to first unread message

?yvind

unread,
Dec 11, 2008, 5:05:03 AM12/11/08
to
I am trying to convert new-style user-defined objects from a superclass to a subclass, and having great difficulty with it.

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

Doug Schwarz

unread,
Dec 11, 2008, 8:10:23 AM12/11/08
to
In article <ghqokf$p20$1...@fred.mathworks.com>,
"?yvind" <oyv...@gmail.com> wrote:

Please post the code for your constructor(s).

--
Doug Schwarz
dmschwarz&ieee,org
Make obvious changes to get real email address.

?yvind

unread,
Dec 12, 2008, 5:54:02 AM12/12/08
to
Doug Schwarz <s...@sig.for.address.edu> wrote in message <see-E7FF6E.0...@news.frontiernet.net>...
> In article <ghqokf$p20$1...@fred.mathworks.com>,


> 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

Doug Schwarz

unread,
Dec 12, 2008, 10:05:57 AM12/12/08
to
In article <ghtfsa$lq9$1...@fred.mathworks.com>,
"?yvind" <oyv...@gmail.com> wrote:

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.

Steven Lord

unread,
Dec 12, 2008, 10:09:57 AM12/12/08
to

"?yvind" <oyv...@gmail.com> wrote in message
news:ghtfsa$lq9$1...@fred.mathworks.com...

> Doug Schwarz <s...@sig.for.address.edu> wrote in message
> <see-E7FF6E.0...@news.frontiernet.net>...
>> In article <ghqokf$p20$1...@fred.mathworks.com>,
>
>> 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

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


?yvind

unread,
Dec 12, 2008, 10:58:02 AM12/12/08
to
"Steven Lord" <sl...@mathworks.com> wrote in message <ghtus5$2a4$1...@fred.mathworks.com>...

>
> 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?

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

?yvind

unread,
Dec 12, 2008, 11:41:02 AM12/12/08
to
Doug Schwarz <s...@sig.for.address.edu> wrote in message <see-5A7EB7.1...@news.frontiernet.net>...

> 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.
>
> --
> Doug Schwarz
> dmschwarz&ieee,org
> Make obvious changes to get real email address.

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

Doug

unread,
Mar 11, 2010, 4:16:05 PM3/11/10
to
I came upon this post by searching on the "object class must be preserved" message. It's not a particularly clear message, and had me chasing my tail for a while.

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.

Paul

unread,
Mar 6, 2012, 1:37:18 PM3/6/12
to
The best solution is to
Replace Constructor with Factory Method as described in Martin Fowler's Refactoring: Improving the Design of Existing Code

classdef PersonType
properties
Type;
end

methods

function obj = PersonType(person)
if nargin ~= 0
obj.Type = person;
end
end

end

methods(Static)
function obj = create(person) %#ok<STOUT>
% factory method for creating people
try
eval(['obj = ', person, ';']);
catch me
error([obj, 'is currently not supported']);
end

end
end

end


classdef Man < PersonType
properties
Gender = 'Male';
end

methods
function obj = Man()
obj = obj@PersonType('Man');
end
end

end


classdef Woman < PersonType
properties
Gender = 'Female';
end

methods
function obj = Woman()
obj = obj@PersonType('Woman');
end
end

end






"Doug " <dougla...@acm.org> wrote in message <hnbmil$990$1...@fred.mathworks.com>...
0 new messages