I've got two new-style MATLAB classes - B
& C
, both concrete subclasses of an abstract parent, A
. A
is a subclass of hgsetset
(handle class). I'd like to put them in an array in MATLAB, and treat them both as A
s. They are defined, roughly, as:
classdef A <hgsetget
methods
function foo(this)
%does some common stuff, then
this.fooWorker;
end
end %public Methods
methods(Abstract, Access=protected)
fooWorker(this);
end %abstract Methods;
end
classdef B < A
methods(Access=protected)
function fooWorker(this)
%implementation
end
end %protected Methods;
end
However if I do this:
arr = [b c]; % where b & c are objects of type B & C respectively.
arr(1).foo;
arr(2).foo;
MATLAB will tell me both are of type B
, and if I call the abstract method from A
that both implement (foo
), it executes, essentially, two copies of b
.
However, if I reverse the order:
arr = [c b];
It tells me that both are of type C
, and if I try to execute foo on both, it executes, essentially two copies of c
.
Any ideas how to use subclasses in a polymorphic way?
I know that I can put them in a cell array and get 90% of what I need. But that is a bit of a kludge.
Unfortunately, all the elements of an array in MATLAB must be of the same type. When you concatenate different classes, MATLAB will attempt to convert them all to the same class.
If you've defined one of your classes as being inferior or superior to the other (using the InferiorClasses attribute or the INFERIORTO/SUPERIORTO functions), then the methods of the more superior class are invoked. If you haven't specified a relationship between the classes, then the two objects have equal precedence and MATLAB calls the left-most object method. This is likely why arr = [b c];
creates an array of class B and arr = [c b];
creates an array of class C.
If you want to execute the foo
method defined for class B on object b
, and also execute the foo
method defined for class C on object c
, then you will likely have to use cell arrays and the function CELLFUN. If foo
doesn't return a value, you could do something like this:
arr = {b,c};
cellfun(@foo,arr); % Invoke foo on each element of the cell array
For fun, I came up with a potential solution which technically works, but has some limitations. To illustrate the idea, I've put together a few sample classes similar to what you listed in the question. Here's the abstract superclass classA
:
classdef classA < hgsetget
properties
stuff
end
properties (Access = protected)
originalClass
end
methods
function foo(this)
disp('I am type A!');
if ~strcmp(class(this),this.originalClass)
this = feval(this.originalClass,this);
end
this.fooWorker;
end
end
methods (Abstract, Access = protected)
fooWorker(this);
end
end
And here's an example of the subclass classB
(classC
is exactly the same with everywhere B
replaced by C
and vice versa):
classdef classB < classA
methods
function this = classB(obj)
switch class(obj)
case 'classB' % An object of classB was passed in
this = obj;
case 'classC' % Convert input from classC to classB
this.stuff = obj.stuff;
this.originalClass = obj.originalClass;
otherwise % Create a new object
this.stuff = obj;
this.originalClass = 'classB';
end
end
end
methods (Access = protected)
function fooWorker(this)
disp('...and type B!');
end
end
end
The constructors for classB
and classC
are designed such that the two classes can be converted to one another. The property originalClass
is initialized at creation and indicates what the original class of the object was. This property will remain unchanged if an object is converted from one class to another.
Within the foo
method, the current class of the object passed in is checked against its original class. If they differ, the object is first converted back to its original class before invoking the fooWorker
method. Here's a test:
>> b = classB('hello'); % Create an instance of classB
>> c = classC([1 2 3]); % Create an instance of classC
>> b.foo % Invoke foo on b
I am type A!
...and type B!
>> c.foo % Invoke foo on c
I am type A!
...and type C!
>> arr = [b c] % Concatenate b and c, converting both to classB
arr =
1x2 classB handle
Properties:
stuff
Methods, Events, Superclasses
>> arr(1).foo % Invoke foo on element 1 (formerly b)
I am type A!
...and type B!
>> arr(2).foo % Invoke foo on element 2 (formerly c)
I am type A!
...and type C!
One key limitation (aside from being a little ugly) is the case where classB
and classC
each have properties that the other does not. In such a case, converting to the other class and then converting back would likely cause those properties to be lost (i.e. reset to their default values). However, if one class were a subclass of the other, such that it had all the same properties and thensome, there is a solution. You could set the subclass to be superior to the superclass (see discussion above) such that concatenating objects of the two classes will always cause the superclass objects to be converted to the subclass. When converted back within "polymorphic" methods (like foo
above), no object data will be lost.
I don't know how workable a solution this is, but maybe it will at least give you some interesting ideas. ;)
You can do this now in R2011a by subclassing matlab.mixin.Heterogeneous. So for example as in your code the abstract class would be:
classdef A < matlab.mixin.Heterogeneous
methods
function foo(this)
disp('In abstract parent');
this.fooWorker;
end
end
methods(Abstract, Access=protected)
fooWorker(this);
end
end
and the subclasses would look like:
classdef B < A
methods(Access=protected)
function fooWorker(this)
disp('In B');
end
end
end
and similarly for a class 'C'. This then gives the following output from MATLAB:
>> b = B;
>> c = C;
>> arr = [b, c];
>> arr(1).foo
In abstract parent
In B
>> arr(2).foo
In abstract parent
In C
>>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With