I'm working on a PHP framework called Carbon CMS. This framework will be used as base to build larger web applications on top of it. This framework should provide all kinds of interfaces such as database access, caching and so on.
Currently I'm working on the IO and file handling part of the framework. This question will be about the design pattern I should use, whether I should use a single, or multiple classes that extend each other. In this case, the question will be asked with file systems in mind, because this applies to the problem I'm having, and should make it easier to understand the question itself because an example is given. So keep in mind, that this design-pattern question also applies to other cases.
My idea is to built a class, or multiple classes that reference to a file, directory or symbolic link on the file system by it's path. Then, these class(es) should also provide an interface to read/write files, to list directories and so on. Think of the File
class in Java, or the File
, Dir
classes in Qt.
The question I'm having is what kind of structure or design pattern I should use in this case. I could create a single File
class that has all the functionality included, not just for files, but also for directories and symbolic link. But I could also create multiple classes, such as Path
, File
, Directory
and SymbolicLink
. So, there are two possible ways I can think of:
- Using a single
File
class. This class holds the reference (path) to a file system entry such as a file or directory. This class includes functionality both for files, directories and symbolic links, such as reading/writing files or listing directory contents. - Using multiple classes, which extend each other. A
Path
class (or something similar) holds the reference (path) to the file system entry such as a file or directory. And only contains the basic functionality which could be used on all three different file system entry types (files, directories and symbolic links). Think of features such asPath::isFile()
,Path::getPath()
orPath::exists()
. Then three other classes are used for the different file system entry types calledFile
,Directory
andSymbolicLink
, all of these classes extend thePath
class to implement it's basic functionality and the path reference. These three classes contain the functionality for that specific entry type. So, methods to read and write files would be found in theFile
class;File::getContents();
, and a method to list the contents of a directory would be placed in theDirectory
class;Directory::list();
.
So, to clarify my question. Should I go for the first option, and create a single File
class which handles everything regarding IO and file handling. Or should I go with the second option and create multiple classes. Where each class is for a specific type of file system entry (except the Path
class, which will be used for all three types).
Now, if going for the first option. Functions such as listContents()
(to list directory contents) could easily be called on a path that references a file. It would be impossible to list the directory contents of a file, thus calls like this would return null
, or something similar indicating the directory couldn't be listed. When using the second option, this would be a bit different. The listContents()
method would only be available for Directory
instances, and these instances should reference to a directory anyway. Of course, it would be possible that the referenced path doesn't exist, but that could be the case with both options.
There are a few pro's and con's for both options. Here are some I can think of:
- Going for the first option, with a single class:
- Casting instances isn't required to use entry specific operations when using
File
's. - Creates one huge interface for everything, which makes it messier and allows certain operations to be used on incompatible file system entry types, such as listing directory contents on a file.
- Casting instances isn't required to use entry specific operations when using
- Going for the second option, with multiple classes:
- Casting instances, and instance type checking is required to use entry specific operations when using
Path
's. - Creates a separate interface for each file system entry type, which makes it less messy, and also prevents the use of most operations on incompatible file system entry types.
- Casting instances, and instance type checking is required to use entry specific operations when using
The framework will be open to the public and will be used by multiple people, thus I'm looking for the option that is the easiest to use, doesn't have a performance impact if possible and the option that suits this ground the best.
Here's some sample code, to show off the very basic structure of both options.
When using a single class, with option one:
class File {
private $path;
public function __construct($path) {
$this->path = $path;
}
public function getPath() {
return $this->path;
}
public function getContents() {
// Return the contents of a file
}
public function listContents() {
// Return the contents of a directory
}
}
When using multiple classes, with option two:
class Path {
private $path;
public function __construct($path) {
$this->path = $path;
}
public function getPath() {
return $this->path;
}
}
class File extends Path {
public function getContents() {
// Return the contents of a file
}
}
class Directory extends Path {
public function listContents() {
// Return the contents of a directory
}
}
I should note that all the methods noted in this question, such as Path::getPath();
or Directory::listContents();
are non-static and have to be called on the instance of the class.
Java uses a single File
class, which would be similar to first option I gave with a single class. There are some classes available such as a DirectoryIterator
but classes like this would be created anyway, and don't depend on the first or second option. On the other hand, Qt uses something similar to the second option I gave, as far as I know.
So finally; What would be the best option, and why?