I am extending an existing class of new functionality and I at doubts about which design solution to use. There are several, each of them having pros and cons. My case is this: I have a file header which has a special format and I am going to read and save it. There is a class named FileHeader which already implements some serialization from/to stream and some other functionality. One item on my task list is to add a certain time stamp functionality. The time stamp should be read/stored as seconds since Jan 1, 1994 00:00:00. However the FileHeader class stores the date and time in two separate variables. Therefore I need to write a conversion from/to seconds to Date and Time. The question is where this functionality should reside. I am using secondsPerDay (60*60*24) and dateOrigin (1/1/1994) as constants.
I see there are the following options:
A) implement the conversion as private methods of the FileHeader class. secondsPerDay and dateOrigin then would be static private constants of the class.
//fileheader.h
class FileHeader
{
private:
static const unsigned secondsPerDate = 60 * 60 * 24;
static const Date dateOrigin;
const Date &m_date;
const Time &m_time;
unsigned convertToSeconds() const; // convert m_date and m_time to seconds
void fromSeconds(unsigned secs); // convert and store to m_date and m_time
public:
void saveToStream(Stream &s) const;
void restoreFromStream(const Stream &s);
//... other methods
}
//fileheader.cpp
const Date FileHeader::dateOrigin = Date(1994, 1, 1);
This is rahter straightforward. But what I do not like is that it adds more responsibility to already quite heavy class. You know the rules: one class = one responsibility. For example maintenance would be hard. If someone decides to change the seconds to minutes or whatever, he would rewrite the methods but if not careful enough probably would leave the static constant secondsPerDay although it is not needed any more. Etc. Moreover I do not like the fact that I must have updated the header file although it affect only implementation details.
B) Do the implementation only in unnamed namespace in the .cpp file and use normal functions and static variables:
namespace
{
const unsigned secondsPerDay = 60 * 60 * 24;
const Date dateOrigin = Date(1994, 1, 1);
unsigned dateTimeToSeconds(const Date &d, const Time &t) ...
Date secondsToDate(unsigned secs) ...
Time secondsToTtime(unsigned secs) ...
}
the save and restore methods of FileHeader whould then call these functions. Well, I like it better. I did not mess the header, the class' FileHeader responsbility did not grow. But if someone decides to change the algorithm to use minutes instead of seconds, he could change the functions but if not careful he would leave the unnecessary secondsPerDay static variable even though it is not needed any more.
C) Use unnamed namespace in FileHeader.cpp and a dedicated class in it.
namespace
{
class TimeConverter
{
private:
static const unsigned secondsPerDay = 60 * 60 * 24;
static const Date dateOrigin;
public:
static unsigned secondsFromDateTime(const Date &date, const Time &time) //implementation here...
static Date dateFromSeconds(unsigned seconds) //implementation here...
static Time timeFromSeconds(unsigned seconds) //implementation here...
};
const Date TimeConverter::dateOrigin = Date(1994, 1, 1);
}
the FileHeader save and restore would then call these static methods e.g.
m_date = TimeConverter::dateFromSeconds(secs);
m_time = TimeConverter::timeFromSeconds(secs);
Personally I chose this solution. It does not mess the header, it visually limits the scope of the static variables so that if someone would change the implementation of TimeConverter from seconds to minutes, it is very likely that he would not leave the unnecessary static variable secondsPerDay... Currently the TimeConverter is not used by any other class (just FileHeader) but if this is changed, it can be easily moved to its own header and source file.
When writing the code I realized that this is my usual way I extend the functionality of existing classes of new implementation details. As I am doing it quite often I am curious about what other people are using and why. According to my experience, 95 % of developers use the option A and extend the class. So here are the questions:
is there any other good and useful option?
do I miss some important aspect or implication of using these options?
UPDATE: following the advice from one of the answers below, I hereby present also option D:
namespace TimeConverter
{
const unsigned secondsPerDay = 60 * 60 * 24;
const Date dateOrigin = Date(1994, 1, 1);
unsigned secondsFromDateTime(const Date &date, const Time &time)
{
return (date - dateOrigin) * secondsPerDay + time.asSeconds();
}
Date dateFromSeconds(unsigned seconds)
{
return dateOrigin + seconds / secondsPerDay;
}
Time timeFromSeconds(unsigned seconds)
{
return Time(seconds % secondsPerDay);
}
}
and a question that follows - how is D better than C and vice versa. What are the pros and cons?