一個面試題:計算時間偏移量,怎麼設計你的程序?

計算時間偏移量,例如,計算當前時間向前偏移 30 秒的時間,我們利用java.util.Calendar很容易實現。

    Calendar cal = Calendar.getInstance();
    cal.setTime(new Date());
    cal.add(Calendar.SECOND, -30);
    System.out.println(cal.getTime());

 

我在進行面試的時候,關於程序設計,有問過應聘者這樣的問題。

那麼,我們怎麼封裝這麼一個工具類呢?這個工具類提供哪些工具方法呢?每個方法又當怎麼實現呢?

下面這段優秀的代碼節選自hutool-DateUtil(hutool-all-4.5.18.jar ,maven座標:cn.hutool:hutool-all:4.5.18),香香的,甜甜的,pretty,graceful,pretty graceful.

坦白說,寫出來這個util並不難,你可以寫出來你的代碼,然後做個比較,看看與優秀代碼的差距。

    // --------------------------------------------------- Offset for now
    /**
     * 昨天
     * 
     * @return 昨天
     */
    public static DateTime yesterday() {
        return offsetDay(new DateTime(), -1);
    }

    /**
     * 明天
     * 
     * @return 明天
     * @since 3.0.1
     */
    public static DateTime tomorrow() {
        return offsetDay(new DateTime(), 1);
    }

    /**
     * 上週
     * 
     * @return 上週
     */
    public static DateTime lastWeek() {
        return offsetWeek(new DateTime(), -1);
    }

    /**
     * 下週
     * 
     * @return 下週
     * @since 3.0.1
     */
    public static DateTime nextWeek() {
        return offsetWeek(new DateTime(), 1);
    }

    /**
     * 上個月
     * 
     * @return 上個月
     */
    public static DateTime lastMonth() {
        return offsetMonth(new DateTime(), -1);
    }

    /**
     * 下個月
     * 
     * @return 下個月
     * @since 3.0.1
     */
    public static DateTime nextMonth() {
        return offsetMonth(new DateTime(), 1);
    }

    /**
     * 偏移毫秒數
     * 
     * @param date 日期
     * @param offset 偏移毫秒數,正數向未來偏移,負數向歷史偏移
     * @return 偏移後的日期
     */
    public static DateTime offsetMillisecond(Date date, int offset) {
        return offset(date, DateField.MILLISECOND, offset);
    }

    /**
     * 偏移秒數
     * 
     * @param date 日期
     * @param offset 偏移秒數,正數向未來偏移,負數向歷史偏移
     * @return 偏移後的日期
     */
    public static DateTime offsetSecond(Date date, int offset) {
        return offset(date, DateField.SECOND, offset);
    }

    /**
     * 偏移分鐘
     * 
     * @param date 日期
     * @param offset 偏移分鐘數,正數向未來偏移,負數向歷史偏移
     * @return 偏移後的日期
     */
    public static DateTime offsetMinute(Date date, int offset) {
        return offset(date, DateField.MINUTE, offset);
    }

    /**
     * 偏移小時
     * 
     * @param date 日期
     * @param offset 偏移小時數,正數向未來偏移,負數向歷史偏移
     * @return 偏移後的日期
     */
    public static DateTime offsetHour(Date date, int offset) {
        return offset(date, DateField.HOUR_OF_DAY, offset);
    }

    /**
     * 偏移天
     * 
     * @param date 日期
     * @param offset 偏移天數,正數向未來偏移,負數向歷史偏移
     * @return 偏移後的日期
     */
    public static DateTime offsetDay(Date date, int offset) {
        return offset(date, DateField.DAY_OF_YEAR, offset);
    }

    /**
     * 偏移周
     * 
     * @param date 日期
     * @param offset 偏移週數,正數向未來偏移,負數向歷史偏移
     * @return 偏移後的日期
     */
    public static DateTime offsetWeek(Date date, int offset) {
        return offset(date, DateField.WEEK_OF_YEAR, offset);
    }

    /**
     * 偏移月
     * 
     * @param date 日期
     * @param offset 偏移月數,正數向未來偏移,負數向歷史偏移
     * @return 偏移後的日期
     */
    public static DateTime offsetMonth(Date date, int offset) {
        return offset(date, DateField.MONTH, offset);
    }

    /**
     * 獲取指定日期偏移指定時間後的時間
     * 
     * @param date 基準日期
     * @param dateField 偏移的粒度大小(小時、天、月等){@link DateField}
     * @param offset 偏移量,正數爲向後偏移,負數爲向前偏移
     * @return 偏移後的日期
     */
    public static DateTime offset(Date date, DateField dateField, int offset) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.add(dateField.getValue(), offset);
        return new DateTime(cal.getTime());
    }

    // ------------------------------------ Offset end ----------------------------------------------

 

爲什麼說這麼代碼比較香呢?你品,你細品!

  • 易讀。注意各個方法尤其是以“offset”開頭的方法的簽名,包括方法名、方法參數,包括javadoc,相當清晰,易讀易理解。另外,這幾個方法整體來看,像極了我們母語中的排比句。
  • 豐富。按不同的時間單位(如秒、分鐘、小時、天、周和月)偏移日期時間值,定義了豐富的方法,各種姿勢滿足你。
  • 易用。除了offsetMinute/offsetSecond等offsetXxx方法,還提供了yesterday / tomorrow / lastWeek / nextWeek / lastMonth /nextMonth等拿來即用的方法,不必再調用offsetXxx。
  • 簡潔。計算時間偏移量的算法是相同的,所以,這些方法內部均調用一個通用的 offset 方法,該方法使用 DateField 枚舉值指定要偏移的時間單位和偏移量。
  • 包容。注意最後那個public的offset(Date date, DateField dateField, int offset)方法,當上面的計算時間偏移量的方法無法滿足使用要求時(當然,已經是應有盡有了),你就可以用它了。

 

同樣,關於本地緩存工具,分享一段我曾經寫的LocalCacheUtil工具類。

import cn.hutool.cache.Cache;
import cn.hutool.cache.CacheUtil;
import lombok.extern.slf4j.Slf4j;

import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * 本地緩存工具
 */
@Slf4j
public class LocalCacheUtil {
    private static Cache<String, Object> lfuCache = CacheUtil.newLFUCache(256, TimeUnit.MINUTES.toMillis(30));
    private static Cache<String, Object> timedCache = CacheUtil.newTimedCache(TimeUnit.DAYS.toMillis(1));//過期時間給個默認值

    /**
     * 從本地緩存獲取數據。如果沒有,則設置。(策略:最少使用原則)
     *
     * @param key
     * @param supplier
     * @param <T>
     * @return
     *
     * @see #lfuCache
     */
    public static <T> T getCache(String key, Supplier<T> supplier) {
        return getCache(key, false, supplier);
    }

    /**
     * 從本地緩存獲取數據。如果沒有,則設置。(策略:最少使用原則)
     *
     * @param key
     * @param cacheNullOrEmpty 是否緩存null或空集合
     * @param supplier
     * @param <T>
     * @return
     *
     * @see #lfuCache
     */
    public static <T> T getCache(String key, boolean cacheNullOrEmpty, Supplier<T> supplier) {
        return getCache(lfuCache, key, null, cacheNullOrEmpty, supplier);
    }

    /**
     * 獲取緩存。如果沒有,則設置
     *
     * @param key
     * @param seconds
     * @param supplier 緩存數據提供者
     * @param <T>
     * @return
     */
    public static <T> T getCache(String key, long seconds, Supplier<T> supplier) {
        return getCache(key, seconds, false, supplier);
    }

    /**
     * 刪除緩存
     *
     * @param key 緩存key
     */
    public static void removeCache(String key) {
        timedCache.remove(key);
    }

    /**
     * 獲取緩存。如果沒有,則設置
     *
     * @param key
     * @param seconds
     * @param cacheNullOrEmpty 是否緩存null或空集合
     * @param supplier         緩存數據提供者
     * @param <T>
     * @return
     */
    public static <T> T getCache(String key, long seconds, boolean cacheNullOrEmpty, Supplier<T> supplier) {
        return getCache(timedCache, key, seconds, cacheNullOrEmpty, supplier);
    }

    private static <T> T getCache(Cache<String, Object> myCache, String key, Long seconds, boolean cacheNullOrEmpty, Supplier<T> supplier) {
        if (myCache.containsKey(key)) {
            return (T) myCache.get(key);
        } else {
            T result = supplier.get();
            if (!cacheNullOrEmpty) {
                if (result == null) {
                    log.info("設置緩存---value爲null,不設置--- key={}", key);
                    return null;
                } else if (result instanceof Collection && ((Collection) result).size() == 0) {
                    log.info("設置緩存---value是個空集合,不設置--- key={}", key);
                    return null;
                }
            }
            log.info("設置緩存 key={}", key);
            if (seconds == null) {
                myCache.put(key, result);
            } else {
                myCache.put(key, result, TimeUnit.SECONDS.toMillis(seconds));
            }
            return result;
        }
    }

}
View Code

 

另外,在這個DateUtil工具類中,有一個棄用的offsetDate方法如下。

    /**
     * 獲取指定日期偏移指定時間後的時間
     * 
     * @param date 基準日期
     * @param dateField 偏移的粒度大小(小時、天、月等){@link DateField}
     * @param offset 偏移量,正數爲向後偏移,負數爲向前偏移
     * @return 偏移後的日期
     * @deprecated please use {@link DateUtil#offset(Date, DateField, int)}
     */
    @Deprecated
    public static DateTime offsetDate(Date date, DateField dateField, int offset) {
        return offset(date, dateField, offset);
    }

作爲一個不斷迭代升級的Java工具庫,顯然hutool不能輕易將之前的方法直接去掉,這會遭到罵孃的。因此,hutool的開發者標記了@Deprecated,並在方法的javadoc裏明確指引出來,調用另一個offset(Date, DateField, int)重載。--——————這是一個優秀編碼風格,標記棄用,請向使用者描述背景(棄用原因)或告知使用者應該怎麼辦。

那麼,現在,我們來思考一下:爲什麼棄用這個offsetDate方法改用offset方法呢?歡迎評論區交流!

我在公司內部的軟件系統中,一直在踐行關於棄用方法的這一優秀編碼行爲。只是後來,隨着開發經驗和意識的增強,在行爲上做了一些調整。即,我不再一味地標記棄用,而是斬草除根,對於不合理的方法,優秀採用的方式是直接幹掉方法並修改對方法的調用,這麼做的出發點有三:①我們是中小型內部企業應用系統,工具或組件都是對內使用,具備內部修改的條件;②團隊成員編碼意識良莠不齊,被明確標記了棄用的方法,有時仍被使用;③最好的方式是一次做好,避免時間一長自己都忘了這些冗餘代碼了。

 

The End.

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章