如何限制类/结构,以便只存在某些预定义的对象?

时间:2011-02-19 10:36:10

标签: c++ class-design

假设您的计划需要跟踪一年中的几个月。每个月都有一个名称和天数。显然,这是您希望在编译时定义的信息,并且您希望限制程序,以便在运行时期间不能定义其他月份信息。当然,您希望方便地访问月份数据而无需复杂的方法调用。这些信息的典型用例松散沿着这些方向:

Month m = GetRandomMonth();
if ( m == FEBRUARY )
    CreateAppointment(2011, m, 28);

// Iterating over all months would be optional, but a nice bonus
for (/* each month m*/)
    cout << "Month " << m.name << " has " << m.num_days << " days." << endl;

而不应该飞的东西包括:

Month m = Month("Jabruapril", 42);  // should give compiler error

Month m = MonthService().getInstance().getMonthByName("February");  // screw this

(我故意使代码尽可能模糊,以表明我不限于任何特定的实现方法。)

解决这个问题最优雅的方法是什么?我正在添加自己的aswer供公众审查,但欢迎其他解决方案。

6 个答案:

答案 0 :(得分:5)

如下:

class Month
{
public:
    static const Month JANUARY;
    static const Month FEBRUARY;
    ...

private:
    Month(const std::string &name, int days) : name(name), days(days) {}

    const std::string   name;
    const int           days;
};

const Month Month::JANUARY = Month("January", 31);
const Month Month::FEBRUARY = Month("February", 28);
...

答案 1 :(得分:1)

如果你不需要国家,最好是使用枚举。否则,您需要Singleton的变体,其中有许多预定义的类实例,而不仅仅是一个。关键是声明构造函数private,以便没有外部方可以实例化该类,并将所有需要的类实例定义为静态成员。

class Month {
  public:
    static const Month JANUARY(...);
    ...
    static const Month DECEMBER(...);

    // public API
private:
  Month(...);

    // private members
};

const Month Month::JANUARY = Month(...);
...
const Month Month::DECEMBER = Month(...);

答案 2 :(得分:1)

Month的构造函数设为私有,并公开静态函数调用getMonth

简而言之,让Month单身!那么这就是基于我从你的问题中理解的内容。

-

编辑:

我想改进您的实施。由于Months不是必需的,因此我删除了它:

class Month
{
public:
    string name;
    int num_days;
public:
    static const Month JANUARY;
    static const Month FEBRUARY;
private:
    Month(string n, int nd) : name(n), num_days(nd) {}
};

const Month Month::JANUARY = Month("January", 31);
const Month Month::FEBRUARY = Month("February", 28);

答案 3 :(得分:1)

我认为有几种方法可以解决这个问题:

1)Month是枚举类型,12个元素表示公历12个月的属性。由于C ++没有明确提供枚举类类型,我们将使用以下任一方法伪造它:

  • 私有构造函数和对实例的公共访问(数组,静态数据成员,全局变量或带有月份数字或名称并返回指针/引用的函数)。这是一个经过修改的Singleton。

  • 验证名称或月份编号的公共构造函数,并填写该类持有的内部知识的天数。这给出了类值语义。

请注意,为此目的,修改过的Singleton(12吨)可能实际上并不正确。您在标题中说“只能存在某些预定义的对象”,但在您的代码中编写Month m = GetRandomMonth();,它会创建一个名为Month的新m对象。所以不仅有某些预定义的对象,你在那里创建一个。看起来您希望按值使用月份,而不仅仅是通过引用预定义的对象。

要执行此操作,您需要Month才能拥有可访问的复制构造函数,并且您可能也需要一个赋值运算符。这意味着它不是Twelveleton(意思是,仅限于12个对象类型),只是只有12个可能不同的,不相等的。通过类比考虑类型char - 在我的实现中只有256个可能的值,但我可以轻松地创建超过256个对象:char x[257] = {0};

2)Month是代表一个月的一般类型。在公历中只有12个实际使用的值(如果你在闰年使用2月的不同值,则为13),但如果你想创建一个Month("Jabruapril", 42)(虚构),或Month("Nisan", 30)(希伯来语),或Month("December", 30)(朱利安改革之前的罗马日历),因为你认为课程定义的属性可以帮助你做你正在做的事,然后你欢迎来到。检查一个月是一个有效的格里高利月,并获得格里高利月,这是一个单独的问题,因为通常创造几个月。

(1)和(2)中的每一个都可能是正确的设计。

如果Month类中有很多逻辑内置假定格里高利历,那么(2)不太可能有用,如果你试图使用这个类,它只会给出错误的答案在希伯来日历中。由于公历不会改变(我们都真心希望),因此在使用无效格里高利月份的案例中,可以测试课程的里程并不多。老实说,如果日历发生变化,我无法预测日历会如何变化,因此我无法编写测试来确保我的代码已准备好进行更改。所以(1)可能是你想要的全部。

如果Month本身并没有做很多事情,那么它就会被整合在一起构成一年而日历智能存在于其他地方,然后(2)可能有用 - 你或其他人将重新使用它来实现其他日历。原则上我赞成为我的课程用户提供尽可能多的灵活性,尽管在实践中有时会给课程带来单元测试的负担,这是不合理的。

答案 4 :(得分:0)

这是我自己的解决方案:

class Month
{
public:
    string name;
    int num_days;
private:
    Month(string n, int nd) : name(n), num_days(nd) {}
    friend class Months;
};

class Months
{
public:
    static const Month JANUARY;
    static const Month FEBRUARY;
    // ...
private:
    Months() {}
};

const Month Months::JANUARY = Month("January", 31);
const Month Months::FEBRUARY = Month("February", 28);
// ...

bool operator==(const Month& lhs, const Month& rhs)
{
    return lhs.name == rhs.name;
}

int main()
{
    cout << Months::JANUARY.name << " " << Months::JANUARY.num_days << endl;
    Month m = Months::FEBRUARY;
    if ( m == Months::FEBRUARY )
        cout << m.name << " " << m.num_days << endl;
    return 0;
}

这似乎运作得很好,虽然我不能迭代几个月。这可以通过将Month对象放在数组中并将各个月定义为数组元素的引用来解决。

答案 5 :(得分:0)

这是带有枚举类的解决方案(需要C ++ 11)。我只用两个月的时间使代码简短。这种设计没有使用面向对象的范例,并且最终变得更加简单,即使初学者也可以理解。 C ++是一种多范式语言。为给定问题提供最简单的解决方案,而不是专注于一种特定的范例,这是值得的。

注意:这是正确考虑以下事实的第一个答案:2月的天数取决于年份。每月的天数不能是月份“对象”的静态属性,因为它取决于年份。必须使用方法或函数来获取每月的天数。

T1