C ++类设计:未命名的命名空间或私有类方法中的类或函数?

时间:2014-07-31 07:49:02

标签: c++ design-patterns class-design unnamed-namespace

我正在扩展现有的一类新功能,我怀疑使用哪种设计解决方案。有几个,每个都有利弊。我的情况是这样的:我有一个特殊格式的文件头,我将阅读并保存它。有一个名为FileHeader的类,它已经实现了从/到流的一些序列化和一些其他功能。我的任务列表中的一项是添加特定时间戳功能。自1994年1月1日00:00:00起,时间戳应以秒为单位读取/存储。但是,FileHeader类将日期和时间存储在两个单独的变量中。因此,我需要编写从/到秒到日期和时间的转换。问题是这个功能应该驻留在哪里。我使用secondsPerDay(60 * 60 * 24)和dateOrigin(1/1/1994)作为常量。

我看到有以下选项:

A)将转换实现为FileHeader类的私有方法。然后,secondsPerDay和dateOrigin将成为该类的静态私有常量。

//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);

这是直截了当的。但我不喜欢的是它为已经相当沉重的课程增加了更多的责任。你知道规则:一个类=一个责任。例如维护很难。如果有人决定将秒数改为分钟或其他什么,他会重写方法,但如果不小心可能会留下静态常量secondsPerDay,尽管不再需要它。等等。此外,我不喜欢我必须更新头文件的事实,尽管它只影响实现细节。

B)仅在.cpp文件中的未命名命名空间中执行实现,并使用普通函数和静态变量:

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) ...
}

然后FileHeader的保存和恢复方法会调用这些函数。好吧,我更喜欢它。我没有弄乱标题,班级' FileHeader的责任没有增长。但是如果有人决定将算法更改为使用分钟而不是秒,他可以更改函数,但如果不小心,他会留下不必要的secondsPerDay静态变量,即使不再需要它。

C)在FileHeader.cpp中使用未命名的命名空间,并在其中使用专用的类。

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);
}

FileHeader保存和恢复将调用这些静态方法,例如

m_date = TimeConverter::dateFromSeconds(secs);
m_time = TimeConverter::timeFromSeconds(secs);

我个人选择了这个解决方案。它没有弄乱标题,它在视觉上限制了静态变量的范围,因此如果有人将TimeConverter的实现从几秒钟改为几分钟,很可能他不会留下不必要的静态变量secondsPerDay ...目前任何其他类(只是FileHeader)都不使用TimeConverter,但如果更改了它,则可以轻松地将其移动到自己的头文件和源文件中。

在编写代码时,我意识到这是我通常的方式,我扩展了现有类的新实现细节的功能。正如我经常这样做,我很好奇其他人正在使用什么以及为什么。根据我的经验,95%的开发人员使用选项A并扩展课程。以下是问题:

  • 还有其他有用且有用的选项吗?

  • 我是否遗漏了使用这些选项的一些重要方面或含义?

更新:根据以下答案之一的建议,我在此提出选项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);
    }
}

以及随后的问题 - D如何比C更好,反之亦然。有什么优点和缺点?

3 个答案:

答案 0 :(得分:1)

就个人而言,我选择了选项B.如果我曾经需要重新使用该功能,我会将其改编为C.但我认为为每一件小事做一个课可以导致过多的膨胀和样板。我喜欢在我需要的时候抽象,而不是在... YAGNI的变体之前抽象。选项B在同一位置定义和使用功能,这使其更易于阅读。另外,正如你所说,它并不会使头文件混乱。

答案 1 :(得分:1)

如果我已正确理解,您要应用的修改只是实现细节。用户永远无法修改它。

这个函数在我看来非常通用,在其他地方很有用,所以我会把它放在一个不同标题的命名空间中。

//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);
}

我将介绍函数secondsPerDay而不是全局变量,只是为了风格和清晰度。我相信性能上的差异是可以忽略的(只有剖析才会证明)。

真正的区别在于使功能需要额外的参数。您将单独测试这些函数,并且可以在FileHeader类以外的其他上下文中重用它们。

最后,在FileHeader.cpp文件中,您将包含标题,您将定义开始日期。

关于选项C的最终评论。没有必要在C ++中创建只使用静态方法的类(例如,在java中需要它,不允许使用自由函数)。命名空间是C ++实现它的方式。

答案 2 :(得分:1)

绝对不要选择A.如果你把它作为一个私人成员,它仍然是该类界面的一部分,这只会使它混乱。

我不会选择C.我不喜欢所有功能和成员都是静态的课程。这并不能说出一种东西。它只是对相关内容进行分组。这就是命名空间的用途。

我和D一起去。我会把它拉出来用它自己的.h和.cpp文件来使写单元测试变得方便,然后#include它只在.cpp文件中,因为它是您班级的实施细节,而不是界面的一部分。