views:

414

answers:

1

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.

+4  A: 

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.

Option 1: Cell arrays

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

Option 2: Fun with jury-rigging polymorphic behavior

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. ;)

gnovice
These are all well thought out, thanks, but ultimately unsatisfying. Basically, Matlab's classes aren't fully polymorphic. Gotta love cell arrays.
Marc