I have a large set of small, related classes linked together by an interface class. All classes implement a static method, which retrieves and processes data specific to the class.
The output of that static method needs to be formatted in at least 2 ways. Since the transformation of one format to the other is always the same and rather trivial (though long), I thought I'd implement it as a concrete, sealed, static method in the superclass.
However, then I run into the following problem:
% (in Superclass.m)
classdef SuperClass < handle
methods (Static, Abstract)
[arg1, arg2] = subsStaticMethod;
end
methods (Sealed, Static)
function [other_arg1, other_arg2] = supersStaticMethod
% Get data here
[arg1, arg2] = (???).subsStaticMethod
% transform data here
% ...
end
end
end
% (in Subclass.m)
classdef SubClass < SuperClass
methods (Static)
function [arg1, arg2] = subsStaticMethod
% Get class-specific data here
% ...
end
end
end
As far as I can see, calling SubClass.supersStaticMethod()
is impossible with this design, because static methods need to be called using the class name explicitly. In other words, there is no way to insert the subclass name instead of the (???)
in SuperClass.supersStaticMethod
above.
Things I've tried:
mfilename('class')
this doesn't work, because this always returns 'SuperClass'
dbstack
does not contain the information that the method is actually being called from a subclass
I know that I can work around this problem by making supersStaticMethod
non-static, and calling the method on a temporary instance (like SubClass().supersStaticMethod()
). Or create a small wrapper method inside each subclass that just calls the superclass method with mfilename('class')
as argument. Or any of a 100 other things that seem equally clumsy.
But I'd really like to know if there is some meta.class
trickery or something that can cleanly solve this problem. All I've found is this dated thread, which processes the MATLAB command line programmatically to get the subclass name.
However, my classes are to be used inside scripts/functions, and command line use is going to be for debugging purposes only...
Any ideas?
Below's my hacky proposal. The idea is that you store the current calling class in a "static variable" of SuperClass
, then query this field and use it in feval
to call the correct subclass' method. Several notes:
- It could only work as long as you're not doing some computations in parallel (i.e. calling
SubClass#.supersStaticMethod
from more than one thread at a time under a shared memory architecture - in which case the calling class field will be overwritten erratically).
SuperClass
's supersStaticMethod
cannot be Sealed
, though the subclasses' versions can.
- The "static variable" is cleared after
SubClass.supersStaticMethod
to ensure that this method is only ever called from a subclass.
- I've added
matlab.mixin.Heterogeneous
as superclass of SuperClass
for the purpose of demonstration.
classdef SuperClass < handle & matlab.mixin.Heterogeneous
properties (Abstract = true, Access = private, Constant = true)
subclass@meta.class scalar;
end
methods (Static, Abstract)
[arg1, arg2] = subsStaticMethod;
end
methods (Sealed, Static)
function out = currentClass(input) % < the closest thing in MATLAB to a static variable
persistent currentClass;
if nargout == 0 && nargin == 1 % "setter" mode
currentClass = input;
out = [];
else % "getter" mode
out = currentClass;
end
end
end
methods (Static)
function [other_arg1, other_arg2] = supersStaticMethod
% Who am I?
whosCalling = SuperClass.currentClass();
if isempty(whosCalling) || ~isa(whosCalling,'meta.class')
[other_arg1,other_arg2] = deal(NaN);
return
else
whosCalling = whosCalling.Name;
end
fprintf(1,'\nCalled from: %s\n', whosCalling);
% Get data here
[arg1, arg2] = feval([whosCalling '.subsStaticMethod']);
% transform data here
other_arg1 = arg1+arg2; other_arg2=[arg1(:);arg2(:)];
fprintf(1,'other_arg1: %s, other_arg2: %s\n',...
num2str(other_arg1), mat2str(other_arg2));
% Clear the current class
SuperClass.currentClass([]);
end
end
end
classdef SubClass1 < SuperClass
properties (Constant)
subclass@meta.class scalar = ?SubClass1;
end
methods (Static)
function [other_arg1, other_arg2] = supersStaticMethod
SubClass1.currentClass(SubClass1.subclass);
[other_arg1, other_arg2] = supersStaticMethod@SuperClass;
end
function [arg1, arg2] = subsStaticMethod
arg1 = -1; arg2 = -2;
end
end % static methods
end % classdef
classdef SubClass2 < SuperClass
properties (Constant)
subclass@meta.class scalar = ?SubClass2;
end
methods (Static)
function [other_arg1, other_arg2] = supersStaticMethod
SubClass1.currentClass(SubClass2.subclass);
[other_arg1, other_arg2] = supersStaticMethod@SuperClass;
end
function [arg1, arg2] = subsStaticMethod
arg1 = 1; arg2 = 2;
end
end % static methods
end % classdef
Then you can test it like this:
function q31269260
arr = [SubClass1, SubClass2];
for ind1 = 1:numel(arr)
arr(ind1).supersStaticMethod;
end
% arr.supersStaticMethod would not work because elements are treated as "instances" of
% SuperClass, whose supersStaticMethod should not be called directly.
The output is:
Called from: SubClass1
other_arg1: -3, other_arg2: [-1;-2]
Called from: SubClass2
other_arg1: 3, other_arg2: [1;2]