第十六章 重構SerialDate
16.1 首先,讓它能工作
- 利用SerialDateTests來完整的理解和重構SerialDate
- 用Clover來檢查單元測試覆蓋了哪些代碼,效果不行
- 重新編寫自己的單元測試
- 經過簡單的修改,讓測試能夠通過
16.2 讓它做對
全過程:
- 開端注釋過時已久,縮短并改進了它
- 把全部枚舉移到它們自己的文件
- 把靜態變量(dateFormatSymbols)和3個靜態方法(getMontShNames、isLeap Year和lastDayOfMonth)移到名為DateUtil的新類中。
- 把那些抽象方法上移到它們該在的頂層類中。
- 把Month.make改為Month.fromInt,并如法炮制所有其他枚舉。為全部枚舉創建了toInt()訪問器,把index字段改為私有。
- 在plusYears和plusMonths中存在一些有趣的重復,通過抽離出名為
correctLastDayOfMonth的新方法消解了重復,使這3個方法清晰多了。 - 消除了魔術數1,用Month.JANUARY.toInt()或Day.SUNDAY:toInt()做了恰當的替換。在SpreadsheetDate上花了點時間,清理了一下算法
?
細節操作:
- 刪除修改歷史
- 導入列表通過使用java.text.*和java.util.*來縮短
- 用<pre>標簽把整個注釋部分包圍起來
- 修改類名:SerialDate => DayDate
- 把MonthConstants改成枚舉
- 去掉serialVersionUID變量,自動控制序列號
- 去掉多余的、誤導的注釋
- EARLIEST_DATE_ORDINAL 和 LATEST_DATE_ORDINAL移到SpreadSheeDate中
- 基類不宜了解其派生類的情況,使用抽象工廠模式創建一個DayDateFactory。該工廠將創建我們所需要的DayDate的實體,并回答有關實現的問題,例如最大和最小日期之類。
- 刪除未使用代碼
- 數組應該移到靠近其使用位置的地方
- 將以整數形式傳遞改為符號傳遞
- 刪除默認構造器
- 刪除final
- 使用枚舉整理for循環,并使用||連接for中的if語句
- 重命名、簡化、重構函數
- 使用解釋臨時變量模式來簡化函數、將靜態方法轉變成實例方法、并刪除重復實例方法
- 算法本身也該有一小部分依賴于實現,將算法上移到抽象類中
最終代碼
DayDate.java
/* ====================================================================
* JCommon : a free general purpose class library forthe Java(tm) platform
* =====================================================================
*
* (C) Copyright 2000-2005, by Object Refinery Limited aand Contributors
...
* /
package org.jfree.date;import java.io.Serializable;
import java.util.*;/*** An abstract class that represents immutable dates with a precision of* one day. The implementation will map each date to an integer that* represents an ordinal number of days from some fixed origin.** Why not just use java.util.Date? We will, when it makes sense. At times,* java.util.Date can be *too* precise - it represents an instant in time,* accurate to 1/1000th of a second (with the date itself depending on the* time-zone). Sometimes we just want to represent a particular day (e.g. 21* January 2015) without concerning ourselves about the time of day, or the* time-zone, or anything else. That's what we've defined DayDate for.** Use DayDateFactory.makeDate to create an instance.** @author David Gilbert* @author Robert C. Martin did a lot of refactoring.*/
public abstract class DayDate implements Comparable, Serializable {public abstract int getOrdinalDay();public abstract int getYear();public abstract Month getMonth();public abstract int getDayOfMonth();protected abstract Day getDayOfWeekForOrdinalZero();public DayDate plusDays(int days) {return DayDateFactory.makeDate(getOrdinalDay() + days);}public DayDate plusMonths(int months) {int thisMonthAsOrdinal = getMonth().toInt() - Month.JANUARY.toInt();int thisMonthAndYearAsOrdinal = 12 * getYear() + thisMonthAsOrdinal;int resultMonthAndYearAsOrdinal = thisMonthAndYearAsOrdinal + months;int resultYear = resultMonthAndYearAsOrdinal / 12;int resultMonthAsOrdinal = resultMonthAndYearAsOrdinal % 12 + Month.JANUARY.toInt();Month resultMonth = Month.fromInt(resultMonthAsOrdinal);int resultDay = correctLastDayOfMonth(getDayOfMonth(), resultMonth, resultYear);return DayDateFactory.makeDate(resultDay, resultMonth, resultYear);}public DayDate plusYears(int years) {int resultYear = getYear() + years;int resultDay = correctLastDayOfMonth(getDayOfMonth(), getMonth(), resultYear);return DayDateFactory.makeDate(resultDay, getMonth(), resultYear);}private int correctLastDayOfMonth(int day, Month month, int year) {int lastDayOfMonth = DateUtil.lastDayOfMonth(month, year);if (day > lastDayOfMonth)day = lastDayOfMonth;return day;}public DayDate getPreviousDayofWeek (Day targetDayofweeek){int offsetToTarget = targetDayOfWeek.toInt() - getDayfWeek().toInt();if(offsetToTarget>=0)offsetToTarget = 7;return plusDays (offsetToTarget);}public DayDate get FollowingDayofWeek (Day targetDayofWeek){int offsetToTarget = targetDayOfWeek.toInt() - getDayofweek().toInt();if(offsetToTarget<= 0)offsetToTarget += 7;return plusDays (offsetToTarget);}public DayDate getNearestDayofWeek (Day targetDayofWeek) {int offsetToThisWeeksTarget = targetDayOfWeek.toInt()- getDayOfWeek().toInt();int offsetToFutureTarget = (offsetToThisWeeksTarget +7)&7;int offsetToPreviousTarget = offsetToFutureTarget - 7if(offsetToFutureTarget>3)return plusDays(offsetToPreviousTarget);elsereturn plusDays (offsetToFutureTarget);}public DayDate getEndOfMonth(){Month month=getMonth();intyear=getYear();int lastDay = DateUtil.lastDayOfMonth (month,year);return DayDateFactory.makeDate(lastDay,month,year);}public Date toDate(){final Calendar calendar = Calendar.getInstance(); int ordinalMonth = getMonth().toInt() - Month.JANUARY.toInt ();calendar.set (getYear(), ordinalMonth, getDayOfMonth(),0,0,0);return calendar.getTime();}public String toString(){return String.format("802d-is-8d", getDayofMonth(),getMonth(),getYear());}publicDaygetDayOfWeek(){Day startingDay = getDayOfWeekForordinalzero();int startingoffset = startingDay.toint() - Day.SUNDAY.toInt();int ordinalofDayOfWeek = (getordinalDay() + startingoffset)7;return Day.fromint(ordinalofDayOfWeek + Day.SUNDAY.toint());}public int daysSince(DayDate date){returngetordinalDay() - date.getordinalDay();}public boolean ison(DayDate other){return getordinalDay() = other.getordinalDay();}public boolean isBefore(DayDate other){return getordinalDay()<other.getordinalDay();}public boolean isonorBefore(DayDate other){return getordinalDay()<= other.getordinalDay();}public boolean isAfter(DayDate other){return getordinalDay() > other.getordinalDay();}public boolean isonorAfter(DayDate other){return getordinalDay() >= other.getordinalDay();}public boolean isInRange(DayDate d1,DayDated2){return isInRange(dl,d2,DateInterval.CLOSED);}public boolean isInRange (DayDate dl, DayDate d2, DateInterval interval){int left = Math.min(dl.getordinalDay(),d2.getordina1Day())int right = Math.max(dl.getOrdinalDay(), d2.getordinalDay());return interval.isIn(getOrdinalDay(),left,right);}
}
Month.java
package org.jfree.date;import java.text.DateFormatSymbols;public enum Month{JANUARY(1), FEBRUARY(2), MARCH(3),APRIL(4), MAY(5), JUNE (6),JULY(7), AUGUST(8), SEPTEMBER(9),OCTOBER(10), NOVEMBER(11), DECEMBER(12);private static DateFormatSymbols dateFormatSymbols = netDateFormatSymbols();private static final int[] LAST_DAY_OF_MONTH = {0,31,28,31,30,31,30,31,31,31,30,31,30,31,30,31};private int index;Month(int index){this.index=index;}public static Month fromInt(int monthIndex) {for(Monthm:Month.values())(if(m.index==monthIndex)return m;}throw new IllegalArgumentException("Invalid month :index " + monthIndex)}public int lastDay(){return LAST_DAY_OF_MONTH[index];}public int quarter(){return1+(index-1)/3;}public String toString()(return dateFormatSymbols.getMonths()[index - 1];}public String toshortString(){return date FormatSymbols.getShortMonths()[index -1];}public static Month parse(String s) {s = s.trim();for(Monthm:Month.values())if(m.matches(s))return m;try{return fromInt(Integer.parseInt(s));}catch (NumberFormatException e){}throw new IllegalArgumentException("Invalid month"+s);}private boolean matches (String s){return s.equalsIgnoreCase(toString()) || s.equalsIgnoreCase(toShortString());}public int toInt(){return index;}
}