简体   繁体   中英

C++ class design: class or functions in unnamed namespace or private class method?

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 eg

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?

Personally, I'd choose option B. If I ever had a need to re-use that functionality, I'd adapt it to C. But I think making a class for every single little thing can lead too much bloat and boilerplate. I prefer to abstract when I need to, not before... a variation on YAGNI . Option B defines and uses the functionality in the same place, which makes it easier to read. Also, as you said, it doesn't clutter up the header file.

If I have understood correctly, the modification that you want to apply are only implementation details. The user will never be able to modify it.

The function seems to me generic enough to be useful elsewhere, so I will put it in a named namespace in a different header.

//date time conversions function header
namespace foo
{
   unsigned secondsPerDay();
   unsigned secondsFromDateTime(
                     const Date &date, 
                     const Time &time, 
                     const Date& startOfTime);
   Date dateFromSeconds(unsigned seconds, const Date& startOfTime);
   Time timeFromSeconds(unsigned seconds, const Date& startOfTime);
}

I will introduce the function secondsPerDay instead of the global variable just for matter of style and clarity. I believe the difference in performance are neglectable (only profiling will tell).

The real difference is making the functions taking an extra parameter. You will test the functions in isolation and you may reuse them in other context than your FileHeader class.

Finally, inside your FileHeader.cpp file you will include the header and you will define your starting date.

A final comment on options C. There is no need to create a class with only static method in C++ (you need it in java, for example, where free functions are not allowed). The named namespace is the C++ way to implement that.

Definitely do not go with A. If you make it a private member, it's still a part of the interface of the class, which just clutters it up.

I wouldn't go with C. I don't like classes where all the functions and members are static. That doesn't really name a type of thing. It's just grouping related stuff. That's what namespaces are for.

I'd go with D. I would pull it out into it's own .h and .cpp file to make writing unit tests convenient, and then #include it only in the .cpp file, since it's an implementation detail of your class, not part of the interface.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM