Call subclass static method from inside superclass

2019-08-08 05:36发布

问题:

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?

回答1:

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]