Полиморфизм Matlab - PullRequest
       13

Полиморфизм Matlab

9 голосов
/ 22 сентября 2009

У меня есть два класса MATLAB нового стиля - B & C, оба конкретных подкласса абстрактного родителя, A. A является подклассом hgsetset (класс дескриптора). Я хотел бы поместить их в массив в MATLAB и рассматривать их как A s. Они определены примерно как:

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

Однако, если я сделаю это:

arr = [b c]; % where b & c are objects of type B & C respectively.
arr(1).foo;
arr(2).foo;

MATLAB скажет мне, что оба имеют тип B, и если я вызываю абстрактный метод из A, который оба реализуют (foo), он выполняет, по сути, две копии b.

Однако, если я переверну порядок:

arr = [c b];

Он говорит мне, что оба имеют тип C, и если я пытаюсь выполнить foo для обоих, он выполняет, по сути, две копии c.

Есть идеи, как использовать подклассы полиморфным способом?

Я знаю, что могу поместить их в массив ячеек и получить 90% того, что мне нужно. Но это немного клудж.

Ответы [ 2 ]

17 голосов
/ 22 апреля 2011

Вы можете сделать это сейчас в R2011a, создав подклассы matlab.mixin.Heterogene. Так, например, как в вашем коде абстрактный класс будет:

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

и подклассы будут выглядеть так:

classdef B < A
methods(Access=protected)
    function fooWorker(this)
        disp('In B');
    end
end
end

и аналогично для класса 'C'. Затем это дает следующий вывод из MATLAB:

>> b = B;
>> c = C;
>> arr = [b, c];
>> arr(1).foo
In abstract parent
In B
>> arr(2).foo
In abstract parent
In C
>> 
5 голосов
/ 22 сентября 2009

К сожалению, все элементы массива в MATLAB должны быть одного типа. Когда вы объединяете разные классы, MATLAB попытается преобразовать их в один класс.

Если вы определили один из ваших классов как более низкий или превосходящий другой (используя атрибут InferiorClasses или INFERIORTO / SUPERIORTO функции), то методы более высокого класса вызываются. Если вы не указали связь между классами, тогда эти два объекта имеют одинаковый приоритет, и MATLAB вызывает самый левый объектный метод. Вероятно, именно поэтому arr = [b c]; создает массив класса B, а arr = [c b]; создает массив класса C.

Вариант 1: Массивы ячеек

Если вы хотите выполнить метод foo, определенный для класса B для объекта b, а также выполнить метод foo, определенный для класса C для объекта c, то вам, вероятно, придется использовать массивы ячеек. и функция CELLFUN . Если foo не возвращает значение, вы можете сделать что-то вроде этого:

arr = {b,c};
cellfun(@foo,arr);  % Invoke foo on each element of the cell array

Вариант 2: Веселье с фальсификацией поведения присяжных

Ради интереса, я предложил потенциальное решение, которое технически работает, но имеет некоторые ограничения. Чтобы проиллюстрировать идею, я собрал несколько примеров классов, аналогичных тем, которые вы перечислили в вопросе. Вот абстрактный суперкласс 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

А вот пример подкласса classB (classC точно такой же, где везде B заменено на C и наоборот):

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

Конструкторы для classB и classC разработаны таким образом, что эти два класса могут быть преобразованы друг в друга. Свойство originalClass инициализируется при создании и указывает, каким был исходный класс объекта. Это свойство останется неизменным, если объект будет преобразован из одного класса в другой.

В методе foo текущий класс передаваемого объекта проверяется по сравнению с его исходным классом. Если они различаются, объект сначала преобразуется обратно в исходный класс, прежде чем вызывать метод fooWorker. Вот тест:

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

Одним из ключевых ограничений (помимо того, что он немного уродлив) является случай, когда classB и classC имеют свойства, которых нет у другого. В таком случае преобразование в другой класс и последующее обратное преобразование может привести к потере этих свойств (т. Е. Сбросить их значения по умолчанию). Однако, если один класс является подклассом другого, так что он имеет все те же свойства и, следовательно, некоторые, есть решение. Вы можете установить, что подкласс превосходит суперкласс (см. Обсуждение выше), так что объединение объектов двух классов всегда приведет к преобразованию объектов суперкласса в подкласс. При обратном преобразовании в «полиморфные» методы (например, foo выше) данные объекта не будут потеряны.

Я не знаю, насколько это работоспособное решение, но, возможно, оно, по крайней мере, даст вам некоторые интересные идеи. ;)

...