继承自类和抽象类

免责声明:此处提供的示例仅用于显示抽象类和继承的使用,可能不一定具有实际用途。此外,在 MATLAB 中没有多态的东西,因此抽象类的使用是有限的。此示例用于显示创建类的人员,从其他类继承并应用抽象类来定义公共接口。

在 MATLAB 中,抽象类的使用相当有限,但它在某些情况下仍然有用。

假设我们想要一个消息记录器。我们可能会创建一个类似于下面的类:

classdef ScreenLogger
    properties(Access=protected)
        scrh;
    end
    
    methods
        function obj = ScreenLogger(screenhandler)
            obj.scrh = screenhandler;
        end
        
        function LogMessage(obj, varargin)
            if ~isempty(varargin)
                varargin{1} = num2str(varargin{1});
                fprintf(obj.scrh, '%s\n', sprintf(varargin{:}));
            end
        end
    end
end

属性和方法

简而言之,属性保持对象的状态,而方法就像接口并定义对象的动作。

scrh 酒店受到保护。这就是它必须在构造函数中初始化的原因。还有其他方法(getter)来访问此属性,但它不能应对此示例。属性和方法可以通过变量访问,该变量通过使用点表示法后跟方法或属性的名称来保存对象的引用:

mylogger = ScreenLogger(1);                         % OK
mylogger.LogMessage('My %s %d message', 'very', 1); % OK
mylogger.scrh = 2;                                  % ERROR!!! Access denied

属性和方法可以是公共的,私有的或受保护的。在这种情况下,protected 表示我可以从继承的类访问 scrh,但不能从外部访问。默认情况下,所有属性和方法都是公共的因此 LogMessage() 可以在类定义之外自由使用。此外,LogMessage 定义了一个接口,这意味着当我们希望对象记录我们的自定义消息时,这是我们必须调用的。

应用

假设我有一个脚本,我使用我的记录器:

clc;
% ... a code
logger = ScreenLogger(1);
% ... a code
logger.LogMessage('something');
% ... a code
logger.LogMessage('something');
% ... a code
logger.LogMessage('something');
% ... a code
logger.LogMessage('something');

如果我有多个地方使用相同的记录器,然后想要将其更改为更复杂的东西,例如在文件中写入消息,我将不得不创建另一个对象:

classdef DeepLogger
    properties(SetAccess=protected)
        FileName
    end
    methods
        function obj = DeepLogger(filename)
            obj.FileName = filename;
        end
        
        function LogMessage(obj, varargin)
            if ~isempty(varargin)
                varargin{1} = num2str(varargin{1});
                fid = fopen(obj.fullfname, 'a+t');
                fprintf(fid, '%s\n', sprintf(varargin{:}));
                fclose(fid);
            end
        end
    end 
end

并只需将代码的一行更改为:

clc;
% ... a code
logger = DeepLogger('mymessages.log');

上面的方法只是打开一个文件,在文件的末尾添加一条消息并关闭它。目前,为了与我的界面保持一致,我需要记住方法的名称是 LogMessage(),但它同样可以是其他任何东西。MATLAB 可以通过使用抽象类强制开发人员使用相同的名称。假设我们为任何记录器定义了一个通用接口:

classdef MessageLogger
    methods(Abstract=true)
        LogMessage(obj, varargin);
    end
end

现在,如果 ScreenLoggerDeepLogger 都继承自此类,则如果未定义 LogMessage(),MATLAB 将生成错误。抽象类有助于构建可以使用相同接口的类似类。

为了这个例子,我将做出稍微不同的改变。我将假设 DeepLo​​gger 将同时在屏幕和文件中执行日志消息。因为 ScreenLogger 已经在屏幕上记录了消息,所以我将从 ScreenLoggger 继承 DeepLogger 以避免重复。除第一行外,ScreenLogger 完全没有变化:

classdef ScreenLogger < MessageLogger
// the rest of previous code 

然而,DeepLogger 需要更多的 LogMessage 方法的变化:

classdef DeepLogger < MessageLogger & ScreenLogger
    properties(SetAccess=protected)
        FileName
        Path
    end
    methods
        function obj = DeepLogger(screenhandler, filename)
            [path,filen,ext] = fileparts(filename);
            obj.FileName = [filen ext];
            pbj.Path     = pathn;
            obj = obj@ScreenLogger(screenhandler);
        end
        function LogMessage(obj, varargin)
            if ~isempty(varargin)
                varargin{1} = num2str(varargin{1});
                LogMessage@ScreenLogger(obj, varargin{:});
                fid = fopen(obj.fullfname, 'a+t');
                fprintf(fid, '%s\n', sprintf(varargin{:}));
                fclose(fid);
            end
        end
    end
end

首先,我只是在构造函数中初始化属性。其次,因为这个类继承自 ScreenLogger,我也必须初始化这个 parrent 对象。这一行更为重要,因为 ScreenLogger 构造函数需要一个参数来初始化它自己的对象。这一行:

obj = obj@ScreenLogger(screenhandler);

简单地说“调用 ScreenLogger 的 consructor 并用屏幕处理程序将其初始化”。值得注意的是,我已将 scrh 定义为受保护。因此,我可以从 DeepLogger 同样访问这个属性。如果该属性被定义为私有。初始化它的唯一方法是使用承包商。

另一个变化是在 methods 节。为了避免重复,我从父类调用 LogMessage() 来在屏幕上记录消息。如果我不得不改变任何东西来改进屏幕日志,现在我必须在一个地方做。其余的代码与 DeepLogger 的一部分相同。

因为这个类也继承自抽象类 MessageLogger,我必须确保 DeepLogger 中的 LogMessage() 也被定义。继承 MessageLogger 在这里有点棘手。我认为它重新定义了 LogMessage 强制性 - 我的猜测。

就应用记录器的代码而言,由于类中的通用接口,我可以放心地保证在整个代码中这一行不会出现任何问题。相同的消息将像以前一样登录屏幕,但另外代码会将此类消息写入文件。

clc;
% ... a code
logger = DeepLogger(1, 'mylogfile.log');
% ... a code
logger.LogMessage('something');
% ... a code
logger.LogMessage('something');
% ... a code
logger.LogMessage('something');
% ... a code
logger.LogMessage('something');

我希望这些例子解释了类的使用,继承的使用以及抽象类的使用。

PS。解决上述问题的方法之一就是其中之一。另一个不那么复杂的解决方案是将 ScreenLoger 作为另一个记录器的组件,如 FileLogger 等 .ScreenLogger 将保存在其中一个属性中。它的 LogMessage 只需调用 ScreenLoggerLogMessage 并在屏幕上显示文字。我选择了更复杂的方法来表示类在 MATLAB 中是如何工作的。以下示例代码:

classdef DeepLogger < MessageLogger
    properties(SetAccess=protected)
        FileName
        Path
        ScrLogger
    end
    methods
        function obj = DeepLogger(screenhandler, filename)
            [path,filen,ext] = fileparts(filename);
            obj.FileName     = [filen ext];
            obj.Path         = pathn;
            obj.ScrLogger    = ScreenLogger(screenhandler);
        end
        function LogMessage(obj, varargin)
            if ~isempty(varargin)
                varargin{1} = num2str(varargin{1});
                obj.LogMessage(obj.ScrLogger, varargin{:}); % <-------- thechange here
                fid = fopen(obj.fullfname, 'a+t');
                fprintf(fid, '%s\n', sprintf(varargin{:}));
                fclose(fid);
            end
        end
    end
end