class Date {
public:
Date(int year, int month, int day) {
...
}
};
К сожалению, не весь мир пользуется логичной нотацией Год/Месяц/День или День/Месяц/Год. Иногда люди пишут Месяц/День/Год. Хотя и первые два легко перепутать. Вот к чему я веду: где-то в далеком от описания класса коде кто-то пишет:Date d(2009, 4, 5);
Что он этим хотел сказать? 4-е Мая или 5-е Апреля? Сложно быть уверенным, что пользователь такого класса когда-нибудь не перепутает порядок аргументов.Можно улучшить дизайн? Да.
Например, так:
class Year {
public:
explicit Year(int year) : year_(year) {}
operator int() const { return year_; }
private:
int year_;
};
И аналогично:class Month { ... };
class Day { ... };
Интерфейс самого класса Date
может быть таким:class Date {
public:
Date(Year year, Month month, Day day);
Date(Month month, Day day, Year year);
Date(Day day, Month month, Year year);
}
И использовать класс надо так:Date d(Year(2010), Month(4), Day(5));
илиDate d(Month(4), Day(5), Year(2010));
Результат будет всегда предсказуем и виден в вызывающем коде. Тут все inline'овое, так что эти три "лишние" класса никакого замедления не дадут.Согласен, писанины немного больше, но зато полная гарантия от опечаток и, как следствие, глупых, но коварных ошибок.
Возражения есть?
>>Возражения есть?
ОтветитьУдалитьВсе верно написано - в таких местах можно и удобно это применять. Можно даже расширить классы и проверять месяц и день на правильность, кидать исключения в дебаге при ошибках и т.п.
А просто месяц надо записывать буквами. Тоже вариант.
ОтветитьУдалитьDate d(2009, 4, 5);
ОтветитьУдалитьа что будет если опять вот так написать со всеми этими перегруженными конструкторами?
а вообще наверно лучше всегда придерживаться http://ru.wikipedia.org/wiki/ISO_8601
ОтветитьУдалитьБудет ошибка компиляции, так как конструкторы вспомогательных типов являются explicit, то есть их неявное использование, например, для преобразования int в Year, запрещено. Если explicit убрать, то съест. Вообще, есть негласное правило хорошего тона - все конструкторы с одним аргументом (а фактически - это конструкторы преобразования или conversion constructors) следует делать explicit, чтобы не иметь их скрытых вызовов, как тут, например. И только когда это явно необходимо, то убирать explicit. Тут как с const - ставь везде, где только можно и убирай потом, если сильно мешает.
ОтветитьУдалитьА по поводу ISO - головы людей сложно переделать. Порой проще дать им надежный интерфейс.
Хороший способ. Можно немного уменьшить количество писанины например вот так
ОтветитьУдалитьhttp://codepad.org/rqCE6irX
Для перечислимых типов лучше использовать enum. Это исключает неоднозначтность в толковании (номера месяца, номера дня недели и т.п.). Кроме того, чтобы не перепутать константы месяцев с локальными переменными.именами классов и т.п., желательно поместить их в соответствующий namespace или область видимости класса (что лучше, т.к. у пользователя не получится сказать "using" и устроить бардак):
ОтветитьУдалитьclass Date {
public:
enum Month {
January, February, March,
April, May, June, July, August,
September, October, November, December
};
Date (Year year, Month month, Day day);
// ...
}; // class Date
Если не использовать "наглых" преобразований (типа Date::Month(i)), код получится читабельнее и проще.
Пользователю класса не нужно искать ответ на вопросы типа "4 - это апрель или май?", его программы тоже становится легче читать и сопровождать.
Поздравляю, вы изобрели именованные параметры :)
ОтветитьУдалитьWelcome to Objective-C!
Ну а вообще, удобная штука.
задумка правильная, но мне не нравится это решение.
ОтветитьУдалитьво-первых потому, что нужно создавать дополнительные классы (в основном мое недовольство заключается в синтаксическом мусоре + создании лишних объектов, хотя не уверен, что тут это сильно попортит)
во-вторых тем, что нужно создать 3! (факториал) конструкторов для всех вариантов дат.
а если в другом случае будет 10 чисел?
имхо, есть 2 более логичных способа.
1) (мне этот нравится больше) основан на идиоме именованного параметра: http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.18
нужно ввести всего 3 метода ВНУТРИ класса и переменную bool is_inited если все 3 значения переданы и они корректны.
2) основан на идиоме именнованного конструктора: http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.8
вообщем-то нужно так же опредять факториал таких методов с именами типа DateYMD() как и в оригинальном способе, но тут это все внутри класса -- этот способ мне нравится меньше, хотя, говорят в с# такой способ создания объектов постоянно используется.
хотя вообщем-то до питоновский именованных аргументов foo(year=2000, month=12, day=31) им всем далеко))
Способ №1 весьма красивый. Только как мне кажестся, тут одной переменной is_inited не обойтись, так как изначально она false и функция для задания каждого аргумента должна сказать, что она вызвана, а is_inited только одна. Получается для каждого аргумента нужна своя is_XXX_inited, или сделать is_inited битовым полем, но тут уже какая-то измена наблюдается ;-)
ОтветитьУдалитьИ кстати, фишка метода, что я описал в том, что в принципе мне не нужно делать факториал конструкторов, и я могу описать только один. Конечно, всем придется пользоваться моей нотацией, и люди могу жаловаться на нелогичность с их точки зрения в порядке параметров, но будет решена полюбому главная задача -- никто не сможет вызвать мой конструктор неправильно, а это уже 99% задачи.
Как насчет такой реализации идеи различимости смысла параметров? Вроде тоже самое в итоге, но код попроще на мой взгляд. И конечно месяцы можно проименовать, как в комментарии до меня.
ОтветитьУдалитьenum Year {};
enum Month {};
enum Day {};
class Date
{
public:
Date(Year year, Month month, Day day);
Date(Day day, Month month, Year year);
Date(Year year, Day day, Month month);
};
используем так же:
Date d(Year(2009), Month(1), Day(28));
Неплохой вариант. Странно как-то получается инициализировать enum числом...
ОтветитьУдалить>>полная гарантия от опечаток
ОтветитьУдалитьПолная гарантия от опечаток бывает только если ничего не писать совсем.
Встретил вчера в сети "C++ FAQ Lite".
ОтветитьУдалитьХотел дать ссылку на идиомы именованных параметров методов и конструктора, но теперь вижу, что товарищ f0b0s меня опередил)) Поразмыслю над универсализацией способа с именованными параметрами метода.
А я бы поступил так: www.pastebin.org/73505
ОтветитьУдалитьЭто, я, конечно, кофию перепил, но всё же чем не вариант. Применять так.
Date pigDay;
...
pigDay.year(1961).month(4).day(15);
Красиво, но тут нарушается атомарность конструирования и инициализации.
ОтветитьУдалитьстатическая функция создания вас спасет, но писаныни будет ещё больше
ОтветитьУдалитьхотя вообщем-то до питоновский именованных аргументов foo(year=2000, month=12, day=31) им всем далеко
ОтветитьУдалитьНу как бэ не далеко.