第十六章 重构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 - 7
if(offsetToFutureTarget>3)
return plusDays(offsetToPreviousTarget);
else
return 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;
}
}