Преобразование MonthConstants в enum инициирует ряд изменений в классе DayDate и всех его пользователях. На внесение всех изменений мне потребовалось около часа. Однако теперь любая функция, прежде получавшая int вместо месяца, теперь получает значение из перечисления Month. Это означает, что мы можем удалить метод isValidMonthCode (строка 326), а также все проверки ошибок кодов месяцев — например, monthCodeToQuarter (строка 356) [G5].
Далее возьмем строку 91, serialVersionUID. Переменная используется для управления сериализацией данных. Если изменить ее, то данные DayDate, записанные старой версией программы, перестанут читаться, а попытки приведут к исключению InvalidClassException. Если вы не объявите переменную serialVersionUID, компилятор автоматически сгенерирует ее за вас, причем значение переменной будет различаться при каждом внесении изменений в модуль. Я знаю, что во всей документации рекомендуется управлять этой переменной вручную, но мне кажется, что автоматическое управление сериализацией надежнее [G4]. В конце концов, я предпочитаю отлаживать исключение InvalidClassException, чем необъяснимое поведение программы в результате того, что я забыл изменить serialVersionUID. Итак, я собираюсь удалить эту переменную — по крайней мере пока.
Комментарий в строке 93 выглядит избыточным. Избыточные комментарии только распространяют лживую и недостоверную информацию [C2]. Соответственно, я удаляю его вместе со всеми аналогами.
В комментариях в строках 97 и 100 упоминаются порядковые номера, о которых говорилось ранее [C1]. Комментарии описывают самую раннюю и самую позднюю дату, представляемую классом DayDate. Их можно сделать более понятными [N1].
public static final int EARLIEST_DATE_ORDINAL = 2; // 1/1/1900
public static final int LATEST_DATE_ORDINAL = 2958465; // 12/31/9999
Мне неясно, почему значение EARLIEST_DATE_ORDINAL равно 2, а не 0. Комментарий в строке 829 подсказывает, что это как-то связано с представлением дат в Microsoft Excel. Более подробное объяснение содержится в производном от DayDate классе с именем SpreadsheetDate (листинг Б.5, с. 428). Комментарий в строке 71 хорошо объясняет суть дела.
Проблема в том, что такой выбор относится к реализации SpreadsheetDate и не имеет ничего общего с DayDate. Из этого я заключаю, что EARLIEST_DATE_ORDINAL и LATEST_DATE_ORDINAL реально не относятся к DayDate и их следует переместить в SpreadsheetDate [G6].
Поиск по коду показывает, что эти переменные используются только в SpreadsheetDate. Они не используются ни в DayDate, ни в других классах JCommon. Соответственно, я перемещаю их в SpreadsheetDate.
Со следующими переменными, MINIMUM_YEAR_SUPPORTED и MAXIMUM_YEAR_SUPPORTED (строки 104 и 107), возникает дилемма. Вроде бы понятно, что если DayDate является абстрактным классом, то он не должен содержать информации о минимальном или максимальном годе. У меня снова возникло искушение переместить эти переменные в SpreadsheetDate [G6]. Тем не менее поиск показал, что эти переменные используются еще в одном классе: RelativeDayOfWeekRule (листинг Б.6, с. 438). В строках 177 и 178 функция getDate проверяет, что в ее аргументе передается действительный год. Дилемма состоит в том, что пользователю абстрактного класса необходима информация о его реализации.
Наша задача — предоставить эту информацию, не загрязняя самого класса DayDate. В общем случае мы могли бы получить данные реализации из экземпляра производного класса, однако функция getDate не получает экземпляр DayDate. С другой стороны, она возвращает такой экземпляр, а это означает, что она его где-то создает. Из строк 187–205 можно заключить, что экземпляр DayDate создается при вызове одной из трех функций: getPreviousDayOfWeek, getNearestDayOfWeek или getFollowingDayOfWeek. Обратившись к листингу DayDate, мы видим, что все эти функции (строки 638–724) возвращают дату, созданную функцией addDays (строка 571), которая вызывает createInstance (строка 808), которая создает SpreadsheetDate! [G7].
В общем случае базовые классы не должны располагать информацией о своих производных классах. Проблема решается применением паттерна АБСТРАКТНАЯ ФАБРИКА [GOF] и созданием класса DayDateFactory. Фабрика создает экземпляры DayDate, а также предоставляет информацию по поводу реализации — в частности, минимальное и максимальное значение даты.
public abstract class DayDateFactory {
private static DayDateFactory factory = new SpreadsheetDateFactory();
public static void setInstance(DayDateFactory factory) {
DayDateFactory.factory = factory;
}
protected abstract DayDate _makeDate(int ordinal);