如何限制类/结构,使其仅某些预定义对象可以存在?
假设您的程序需要跟踪一年中的月份。每个月都有一个名称和长度(以天为单位)。显然,这是您想要在编译时定义的信息,并且您想要限制您的程序,以便在运行时不能定义其他月份信息。当然,您希望方便地访问月份数据,而不需要复杂的方法调用。此信息的典型用例松散是这样的:
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
我故意使代码尽可能模糊,以表明我不限于任何特定的实现方法。)
解决这个问题最优雅的方法是什么?我添加了自己的问题以供公众审查,但欢迎其他解决方案。
Suppose your program needs to keep track of, say, months of the year. Each month has a name and a length in days. Obviously, this is information that you want to define at compile time, and you want to limit your program so that no other month information can be defined during runtime. And of course you want to conveniently access month data without complicated method calls. The typical use cases for this information would loosely be along these lines:
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;
Whereas things that shouldn't fly include:
Month m = Month("Jabruapril", 42); // should give compiler error
Month m = MonthService().getInstance().getMonthByName("February"); // screw this
(I deliberately made the code as vague as possible to signify that I'm not limited to any particular implementation approach.)
What's the most elegant way of solving this problem? I'm adding my own aswer for public review, but other solutions are welcome.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
怎么样:
How about something like:
将
Month
的构造函数设为私有,并公开静态函数调用getMonth
。简而言之,让
Month
单例!嗯,这就是根据我从你的问题中了解到的内容。--
编辑:
我想改进您的实施。由于
Months
不是必需的,所以我删除了它:Make
Month
's constructor private, and expose a static function callgetMonth
.In short, make
Month
singleton! Well this is what based on what I understand from your question.--
Edit:
I would like to improve your implementation. As
Months
is not required, so I removed it:如果不需要状态,最好是使用枚举。否则,您需要 Singleton 的变体,其中有许多该类的预定义实例,而不是不仅仅是一个。其关键是将构造函数声明为私有,以便外部各方无法实例化该类,并将该类的所有需要的实例定义为静态成员。
If you don't need state, the best is to use an enum. Otherwise, you need a variation of Singleton where there is a number of predefined instances of the class, rather than just one. The key to this is declaring the constructor
private
, so that no external parties can instantiate the class, and define all needed instances of the class as static members.我认为有几种方法可以看待这个问题:
1)
Month
是一个枚举类型,有 12 个元素代表公历 12 个月的属性。由于 C++ 没有显式提供枚举类类型,因此我们将使用以下任一方法来伪造它:私有构造函数和对实例的公共访问(数组、静态数据成员、全局变量或需要月份数字或的函数)名称并返回指针/引用)。这是一个修改后的单例。
公共构造函数,用于验证名称或月份编号,并填写类持有的内部知识的天数。这给出了类值语义。
请注意,为此目的,修改后的 Singleton(12 吨)实际上可能并不正确。您在标题中说“只有某些预定义对象可以存在”,但在代码中您编写了
Month m = GetRandomMonth();
,这会创建一个新的Month
对象,名为m
。因此,不仅存在某些预定义的对象,您还可以在那里创建一个对象。看起来您想按值使用月份,而不仅仅是通过引用预定义对象。为此,您需要
Month
拥有一个可访问的复制构造函数,并且您可能还需要一个赋值运算符。这意味着它不是 Twelveleton(即限制为该类型的 12 个对象),只是只有 12 个可能的不同的、不相等的值。类比一下char
类型 - 只有 256 个可能的值(在我的实现中),但我可以轻松创建超过 256 个对象:char x[257] = {0};< /代码>。
2)
Month
是表示月份的通用类型。在公历中实际使用只有 12 个这种类型的值(如果您对闰年的二月使用不同的值,则为 13 个),但如果您想创建Month(" Jabruapril", 42)
(虚构),或月份("Nisan", 30)
(希伯来语),或月份("十二月", 30)
(朱利安改革之前的罗马历法),因为您认为该类定义的属性将帮助您完成您正在做的事情,那么欢迎您。检查月份是否是有效的公历月份以及获取公历月份是与创建一般月份不同的问题。(1) 和 (2) 中的每一个都可能是正确的设计。
如果
Month
类中内置了很多采用公历的逻辑,那么 (2) 不太可能有任何用处,如果您尝试使用该类,它只会给出错误的答案在希伯来历中。由于公历永远不会改变(我们都真诚地希望),因此在使用无效公历月份的情况下使类可测试的里程并不多。老实说,我无法预测如果日历确实发生变化,它会如何变化,因此我无法编写测试来确保我的代码已准备好进行更改。所以 (1) 可能就是您想要的。如果
Month
本身并没有做很多事情,它只是将一些东西组合在一起组成一年,并且日历智能位于其他地方,那么 (2) 可能会有用 - 您或其他人将重用它来实现其他日历。原则上,我赞成为我的类的用户提供尽可能多的灵活性,尽管在实践中有时它会给类带来不合理的单元测试负担。I think there are a couple of ways to look at this:
1)
Month
is an enumerated type, with 12 elements representing properties of the 12 months of the Gregorian calendar. Since C++ doesn't explicitly offer enumerated class types, we'll fake it with either:private constructors and public access to instances (an array, static data members, globals, or function(s) that take a month number or name and return a pointer/reference). This is a modified Singleton.
public constructors that validate the name or month number, and fill in the number of days from internal knowledge held by the class. This gives the class value semantics.
Note that for this purpose a modified Singleton (12-ton) might not actually be right. You say in your title, "only certain predefined objects can exist", but in your code you write
Month m = GetRandomMonth();
, which creates a newMonth
object, namedm
. So there aren't only certain predefined objects, you're creating one right there. It looks like you want to use months by value, not just by reference to predefined objects.To do this you need
Month
to have an accessible copy constructor, and you'll probably want an assignment operator too. That means it's not a Twelveleton (meaning, restricted to 12 objects of the type), it's just that there are only 12 possible distinct, non-equal values. Consider by analogy the typechar
- there are only 256 possible values (on my implementation), but I can easily create more than 256 objects:char x[257] = {0};
.2)
Month
is a general type representing a month. There are only 12 values of this type actually used in the Gregorian calendar (13 if you use a different value for February in leap-years), but if you want to create aMonth("Jabruapril", 42)
(fictional), or aMonth("Nisan", 30)
(Hebrew), or aMonth("December", 30)
(Roman calendar prior to Julian reform), because you think the defined properties the class will help you with what you're doing, then you're welcome to. Checking that a month is a valid Gregorian month, and getting hold of Gregorian months, is a separate concern from creating months in general.Each of (1) and (2) is potentially the right design.
If there's a lot of logic built in to the
Month
class that assumes Gregorian calendar, then there's unlikely to be any use for (2), it will just give the wrong answers if you try to use the class in a Hebrew calendar. Since the Gregorian calendar won't ever change (we all sincerely hope), there's not a lot of mileage in making the class testable with cases that aren't valid Gregorian months. I honestly can't predict how the calendar would change if it did change, so I can't write tests to ensure my code is ready for change. So (1) is probably all you'll ever want.If
Month
doesn't do a great deal of itself, it's just something that gets plugged together to make up a year and the calendar intelligence lies elsewhere, then (2) could be useful - you or someone else will re-use it to implement other calendars. In principle I'm in favour of offering users of my classes as much flexibility as possible, although in practice sometimes it introduces a burden of unit-testing on the class that's not justified.这是我自己的解决方案:
这似乎工作得很好,尽管我无法迭代几个月。不过,可以通过将
Month
对象放入数组中并将各个月份定义为对数组元素的引用来解决此问题。Here's my own solution:
This seems to work well enough, though I can't iterate over months. That could be fixed however by placing the
Month
objects in an array and defining the individual months as references to the array elements.这是一个带有枚举类的解决方案(需要 C++11)。我只用了两个月的时间来保持代码简短。这个设计没有使用面向对象的范式,结果简单得多,即使是初学者也能理解。 C++ 是一种多范式语言。针对给定问题采用最简单的解决方案而不是专注于某一特定范式是值得的。
注意:这是第一个正确考虑这一事实的答案,即二月的天数根据年份而变化。每月的天数不能是月份“对象”的静态属性,因为它取决于年份。人们必须使用方法或函数来获取每月的天数。
Here is a solution with an enum class (C++11 required). I use only two months to keep code short. This design is not using the object oriented paradigm and ends up much simpler, even beginners can understand this. C++ is a multi-paradigm language. It pays off to go the for simplest solution for a given problem instead of focussing on one particular paradigm.
Note: this is the first answer which properly considers the fact, that February has a variable number of days depending on the year. The number of days per month cannot be a static property of the month "object", because it depends on the year. One has to use method or a function to get the days per month.