|Ask a Question||Search PSRCHIVE:|
PSRCHIVE Design PhilosophyModularity and extensibility are desireable software design features; but what does this mean, on a practical level, when developing PSRCHIVE? The following handy rules of thumb can help when making design decisions:
Avoid adding new methods to container classesAs much as possible, avoid the urge to add new methods to the container classes. Container classes include
The imprudent addition of member functions causes a class to bloat over time, making it harder to understand its purpose or relationship with the rest of the software. It also results in much more time spent compiling the software, since all code that uses a class will need to be recompiled when it is modified.
If your new algorithm must be declared as a friend, or if it requires access to private members of the container classes, then it may be best implemented as a member function. This is generally true of any method that must resize the container.
Implement algorithms as classesThere are a number of reasons to implement an algorithm as a class:
1) Clarity of code: Many well-designed algorithms support a variety of options for customization. These options can either be passed as arguments to a function or be set as attributes of a class. The following example demonstrates the descriptive power of the latter option:
// Call a function to compute S/N double SNR = snr (archive, 3.0, 0.1, false);versus
// Use a well-designed class to compute S/N Pulsar::SignalToNoise snr; snr.set_threshold( 3.0 ); snr.set_duty_cycle( 0.1 ); snr.set_use_weights( false ); double SNR = snr.get_snr( archive );Although the function requires only one line of code, the meaning of that line is not at all clear until reference is made to the manual. A good programmer would have documented the meanings of each of the arguments using internal comments; but we are not always good.
The interface to the well-designed class introduces a form of self-documentation. Although it required a little more typing to achieve the same result, the meaning of your code will become a little more clear to everyone else (including yourself in a few months time).
2) Inheritance: Even if you don't realize it at the time, your algorithm may belong to a family of algorithms. By implementing it as a class, you open up the possibility of having your class inherited and/or incorporated into a family tree of similar classes with a similar purpose. This increases the opportunity for code re-use, and can often help others to better understand where your code fits into the big picture.
3) Optimization: Each time you call a function, it must perform
all of its computations given the information provided as arguments.
Each time you call the member function of a class, it can make use of
the results of computations already stored in its attributes. A
simple example of this principle is found in the family of
Use extensions to customize functionalityIf an algorithm must be adapted according to the type of the container, then first consider adding an Extension class to implement this change. For example, the
The use of Extension classes enables the modular composition of different behaviours into a class. Without this design technique, it would be necessary to either duplicate the code or multiply inherit from different base classes that provide the different pieces of functionality.