I am writing a program that manipulates encrypted virtual disks and encrypted flash drives (eg. importing disks into my application, mounting/unmounting, changing passwords etc.). I am still new to object-oriented design so I have run into the following problem.
I have identified three disk types:
- Disk stored in a file (virtual disk) on a hard-drive (fixed location)
- Disk stored in a file (virtual disk) on a removable device (eg. flash drive, absolute path can change)
- Encrypted flash drive (same as above but stored directly on the device so no filepath)
The operations i need to do over these disks are mounting, unmounting, checking for presence on the computer and changing password. The main problem i have is with the differences (and similarities) in storing their location (file on a hard-drive can do with only an absolute filepath; encrypted device can do with a device ID; file on a removable device needs both device ID and a relative path on the device).
I have came up with multiple solutions so far but none of which I liked. Is there a preferred way to do this? Or am I thinking about this the wrong way?
Edit: The solutions I came up with so far:
Solution 1:
- Creating an abstract base Disk class with abstract methods for the operations i need (and some data about mounting which is common to all disks).
- From the base class I derive a PhysicalDisk class and a VirtualDisk class. VirtualDisk stores its location as a fixed path (string) and PhysicalDisk stores its location in a RemovableDevice class (contains a device ID, status flags etc.). Both classes implement the operations from the base class with respect to how their location is stored.
- From the VirtualDisk class i then derive a RemovableVirtualDisk class, which adds a RemovableDevice class as a member and reimplements some of the parent methods to account for this change.
The problem is that the implemented methods in derived classes are very similar, varying mostly in respect to how the location of the disk is stored.
Solution 2:
- Creating one class for all disk types, eg. EncryptedDisk, which then stores the disk location using polymorphism, in an abstract DiskLocation class. From DiskLocation I derive a FileOnFixedDevice class (for virtual disks) and a RemovableDevice class (for encrypted drives). From that i further derive a FileOnRemovableDevice class (for virtual disks on removable drives).
- The classes implement a GetSystemLocation() method and a IsDeviceInSystem() method, which are then used uniformly in the EncryptedDisk class for all disk types.
The problem I had with this solution is that I perform checks for device availability even when it does not make sense (disks on hard-drives). Also I might need to have some specific information about the disk location in the future, not just polymorphic objects which could be anything.
Maybe the optimal design is somewhere in the middle? Or would some different approach solve my problems?
2
The problem is that the implemented methods in derived classes are
very similar, varying mostly in respect to how the location of the
disk is stored.
This is not a problem. The point of object-oriented design is not to reduce the total amount of code by factoring common code into superclass methods. The point of object oriented design is proper modelling of the real-world entities. So, it is perfectly fine that many of the methods are very similar.
If you can think of ways in which you can factor common code out of the derived classes and into new methods of the superclass, do that only if the superclass methods have well defined purposes that actually make sense for the superclass when examined on its own, without any knowledge of its subclasses.
Both of the approaches that you described look sound, so I do not know which one of them you should prefer, but whatever you do, try to keep the total number of classes (and the total amount of code, if possible) you need to represent your model as small as possible, because it is quite likely to change as you gain a better understanding of the situation, so the smaller it is, the easier it will be to change it.
In light of that, the first approach might be preferable, because it looks smaller.