Disclaimer: the examples presented here are only for the purpose of showing the use of abstract classes and inheritance and may not necessarily be of a practical use. Also, there is no sich thing as polymorphic in MATLAB and therefore the use of abstract classes is limited. This example is to show who to create a class, inherit from another class and apply an abstract class to define a common interface.
The use of abstract classes is rather limited in MATLAB but it still can come useful on a couple of occasions.
Let's say we want a message logger. We might create a class similar to the one below:
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
Properties and methods
In short, properties hold a state of an object whilst methods are like interface and define actions on objects.
The property scrh
is protected. This is why it must be initialized in a constructor. There are other methods (getters) to access this property but it is out of cope of this example. Properties and methods can be access via a variable that holds a reference to an object by using dot notation followed by a name of a method or a property:
mylogger = ScreenLogger(1); % OK
mylogger.LogMessage('My %s %d message', 'very', 1); % OK
mylogger.scrh = 2; % ERROR!!! Access denied
Properties and methods can be public, private, or protected. In this case, protected means that I will be able to access to scrh
from an inherited class but not from outside. By default all properties and methods are public. Therefore LogMessage()
can freely be used outside the class definition. Also LogMessage
defines an interface meaning this is what we must call when we want an object to log our custom messages.
Application
Let's say I have a script where I utilize my logger:
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');
If I have multiple places where I use the same logger and then want to change it to something more sophisticated, such as write a message in a file, I would have to create another object:
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
and just change one line of a code into this:
clc;
% ... a code
logger = DeepLogger('mymessages.log');
The above method will simply open a file, append a message at the end of the file and close it. At the moment, to be consistent with my interface, I need to remember that the name of a method is LogMessage()
but it could equally be anything else. MATLAB can force developper to stick to the same name by using abstract classes. Let's say we define a common interface for any logger:
classdef MessageLogger
methods(Abstract=true)
LogMessage(obj, varargin);
end
end
Now, if both ScreenLogger
and DeepLogger
inherit from this class, MATLAB will generate an error if LogMessage()
is not defined. Abstract classes help to build similar classes which can use the same interface.
For the sake of this exmaple, I will make slightly different change. I am going to assume that DeepLogger will do both logging message on a screen and in a file at the same time. Because ScreenLogger
already log messages on screen, I am going to inherit DeepLogger
from the ScreenLoggger
to avoid repetition. ScreenLogger
doesn't change at all apart from the first line:
classdef ScreenLogger < MessageLogger
// the rest of previous code
However, DeepLogger
needs more changes in the LogMessage
method:
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
Firstly, I simply initialize properties in the constructor. Secondly, because this class inherits from ScreenLogger
I have to initialize this parrent object as well. This line is even more important because ScreenLogger
constructor requires one parameter to initalize its own object. This line:
obj = obj@ScreenLogger(screenhandler);
simply says "call the consructor of ScreenLogger and initalize it with a screen handler". It is worth noting here that I have defined scrh
as protected. Therefore, I could equally access this property from DeepLogger
. If the property was defined as private. The only way to intialize it would be using consuctor.
Another change is in section methods
. Again to avoid repetition, I call LogMessage()
from a parent class to log a message on a screen. If I had to change anything to make improvements in screen logging, now I have to do it in one place. The rest code is the same as it is a part of DeepLogger
.
Because this class also inherits from an abstract class MessageLogger
I had to make sure that LogMessage()
inside DeepLogger
is also defined. Inheritting from MessageLogger
is a little bit tricky here. I think it cases redefinition of LogMessage
mandatory--my guess.
In terms of the code where the a logger is applied, thanks to a common interface in classes, I can rest assure ther changin this one line in the whole code would not make any issues. The same messages will be log on screen as before but additionally the code will write such messages to a file.
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');
I hope these examples explained the use of classes, the use of inheritance, and the use of abstract classes.
PS. The solution for the above problem is one of many. Another solution, less complex, would be to make ScreenLoger
to be a component of another logger like FileLogger
etc. ScreenLogger
would be held in one of the properties. Its LogMessage
would simply call LogMessage
of the ScreenLogger
and show text on a screen. I have chosen more complex approach to rather show how classes work in MATLAB. The example code below:
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