I have different types of files that I extract from and store data in, for example .csv and database files. They are usually large files and I already have appropriate methods for extracting data from them.
Let's use the class textfile
as an example. I'd like to be able to create an array of textfile
objects where each element corresponds to a unique text file. I'd like to be able to call textfile
with a single argument, or no argument at all.
If I pass no argument, I'll be taken to a file select screen to select the files, and an object array will be created, one element for each file I selected.
If one argument is passed, I'd like the array to be created depending on what that argument was. If it was a directory path, open up the file select screen again. If it was a cell array of filepaths, create an object array of those files.
I'd like to be able to easily create more classes like textfile
that have the same basic behaviour.
I've defined a superclass file
to take care of behaviour common to all files. This includes things like assigning properties such as filename
and extension
. My other classes are all subclasses of file
.
In the constructor for file
, I have two input arguments. My idea was that in the relevant subclass constructors I'd call the file
constructor. The first input argument would be the type of file, e.g. txt
, while the second would be the argument I chose when creating the textfile
array. For simplicity, let's say the only type of arg
I want to be able to handle is a directory path from where to choose files from.
classdef textfile < file
methods
function textfileObject = textfile(arg)
if nargin == 0
arg = '';
end
textfileObject@file('txt',arg);
end
end
The file
constructor, depending on what arg
is when I create textfile
objects, generates an array of objects in accordance with using the No Input Argument Constructor Requirement.
classdef file < handle
properties
Path
end
methods
function FileObject = file(FileType,arg)
if nargin == 2
FileList = file.SelectFiles(FileType,arg);
FileObject(numel(FileList),1) = file;
for filecount = 1:numel(FileObject)
FileObject(filecount,1).Path = FileList{filecount};
end
end
end
end
methods(Static)
function FileList = SelectFiles(DirectoryPath)
% Some selection dialogs. Returns a cell array of filepaths
end
end
end
This works when arg
is a directory, because when the object array is initalized in textfile
with no input argument, arg
is set to ''
which works with the rest of the constructors.
However, I want to be able to not have any input arguments when creating textfile
objects, but that doesn't work with the No Input Argument rules for constructors.
Is there a way to create object arrays while
calling a superclass constructor and
not using any input arguments?
I'm open to other solutions that can help with my problem of creating classes for each of my file types.
It turns out you can call the subclass constructor from the superclass using an anonymous function. In effect, you can call the subclass constructor with no input arguments, and then generate the filenames in the superclass before calling the subclass again (recursively) with these filenames as inputs. Thanks @Suever.
The subclass constructor will simply be:
classdef textfile < file
methods
function textfileObject = textfile(varargin)
textfileObject@file('txt',varargin{:});
end
end
end
And in the superclass:
classdef file < handle
properties
Path
end
methods
function FileObject = file(FileType,varargin)
% Subclass constructor handle
Constructor = @(FilePath)feval(class(FileObject),FilePath);
% No subclass arguments
if nargin == 1
FileList = file.SelectFiles(FileType,'');
for a = 1:numel(FileList)
FileObject(a,1) = Constructor(FileList{a});
end
% One subclass argument
elseif nargin == 2
arg = varargin{1};
if ischar(arg)
FileStruct = dir(arg);
if numel(FileStruct) == 1
FileObject.Path = arg;
end
end
% Too many subclass arguments
else
error('Subclasses of file only take one or no input arguments');
end
end
end
end
The way to do this would be to call the constructor of your class recursively to generate the array of textfile
objects.
classdef textfile < file
methods
function self = textfile(arg)
if ~exist('arg', 'var')
% Get list of files somehow
[fnames, pname] = uigetfile('MultiSelect', 'on');
filelist = fullfile(pname, fnames);
% Call the constructor again for each file
output = cellfun(@textfile, filelist, 'uniformoutput', 0);
% Flatten the cell array of objects into an array of the right shape
self = reshape([output{:}], size(filelist));
else
% Do default construction here
self = self@file('txt', arg);
end
end
end
end
If you want to implement this in the base class, you could change the recursive call to be something like the following which would call the correct subclass constructor from within the file
superclass
constructor = @(varargin)feval(class(self), varargin{:});
output = cellfun(constructor, filelist, 'uniformoutput', 0);