一个面试题:计算时间偏移量,怎么设计你的程序?

笑看天下无敌手  金牌会员 | 2023-7-19 15:32:37 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 974|帖子 974|积分 2922


计算时间偏移量,例如,计算当前时间向前偏移 30 秒的时间,我们利用java.util.Calendar很容易实现。
  1.     Calendar cal = Calendar.getInstance();
  2.     cal.setTime(new Date());
  3.     cal.add(Calendar.SECOND, -30);
  4.     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并不难,你可以写出来你的代码,然后做个比较,看看与优秀代码的差距。
  1.     // --------------------------------------------------- Offset for now
  2.     /**
  3.      * 昨天
  4.      *
  5.      * @return 昨天
  6.      */
  7.     public static DateTime yesterday() {
  8.         return offsetDay(new DateTime(), -1);
  9.     }
  10.     /**
  11.      * 明天
  12.      *
  13.      * @return 明天
  14.      * @since 3.0.1
  15.      */
  16.     public static DateTime tomorrow() {
  17.         return offsetDay(new DateTime(), 1);
  18.     }
  19.     /**
  20.      * 上周
  21.      *
  22.      * @return 上周
  23.      */
  24.     public static DateTime lastWeek() {
  25.         return offsetWeek(new DateTime(), -1);
  26.     }
  27.     /**
  28.      * 下周
  29.      *
  30.      * @return 下周
  31.      * @since 3.0.1
  32.      */
  33.     public static DateTime nextWeek() {
  34.         return offsetWeek(new DateTime(), 1);
  35.     }
  36.     /**
  37.      * 上个月
  38.      *
  39.      * @return 上个月
  40.      */
  41.     public static DateTime lastMonth() {
  42.         return offsetMonth(new DateTime(), -1);
  43.     }
  44.     /**
  45.      * 下个月
  46.      *
  47.      * @return 下个月
  48.      * @since 3.0.1
  49.      */
  50.     public static DateTime nextMonth() {
  51.         return offsetMonth(new DateTime(), 1);
  52.     }
  53.     /**
  54.      * 偏移毫秒数
  55.      *
  56.      * @param date 日期
  57.      * @param offset 偏移毫秒数,正数向未来偏移,负数向历史偏移
  58.      * @return 偏移后的日期
  59.      */
  60.     public static DateTime offsetMillisecond(Date date, int offset) {
  61.         return offset(date, DateField.MILLISECOND, offset);
  62.     }
  63.     /**
  64.      * 偏移秒数
  65.      *
  66.      * @param date 日期
  67.      * @param offset 偏移秒数,正数向未来偏移,负数向历史偏移
  68.      * @return 偏移后的日期
  69.      */
  70.     public static DateTime offsetSecond(Date date, int offset) {
  71.         return offset(date, DateField.SECOND, offset);
  72.     }
  73.     /**
  74.      * 偏移分钟
  75.      *
  76.      * @param date 日期
  77.      * @param offset 偏移分钟数,正数向未来偏移,负数向历史偏移
  78.      * @return 偏移后的日期
  79.      */
  80.     public static DateTime offsetMinute(Date date, int offset) {
  81.         return offset(date, DateField.MINUTE, offset);
  82.     }
  83.     /**
  84.      * 偏移小时
  85.      *
  86.      * @param date 日期
  87.      * @param offset 偏移小时数,正数向未来偏移,负数向历史偏移
  88.      * @return 偏移后的日期
  89.      */
  90.     public static DateTime offsetHour(Date date, int offset) {
  91.         return offset(date, DateField.HOUR_OF_DAY, offset);
  92.     }
  93.     /**
  94.      * 偏移天
  95.      *
  96.      * @param date 日期
  97.      * @param offset 偏移天数,正数向未来偏移,负数向历史偏移
  98.      * @return 偏移后的日期
  99.      */
  100.     public static DateTime offsetDay(Date date, int offset) {
  101.         return offset(date, DateField.DAY_OF_YEAR, offset);
  102.     }
  103.     /**
  104.      * 偏移周
  105.      *
  106.      * @param date 日期
  107.      * @param offset 偏移周数,正数向未来偏移,负数向历史偏移
  108.      * @return 偏移后的日期
  109.      */
  110.     public static DateTime offsetWeek(Date date, int offset) {
  111.         return offset(date, DateField.WEEK_OF_YEAR, offset);
  112.     }
  113.     /**
  114.      * 偏移月
  115.      *
  116.      * @param date 日期
  117.      * @param offset 偏移月数,正数向未来偏移,负数向历史偏移
  118.      * @return 偏移后的日期
  119.      */
  120.     public static DateTime offsetMonth(Date date, int offset) {
  121.         return offset(date, DateField.MONTH, offset);
  122.     }
  123.     /**
  124.      * 获取指定日期偏移指定时间后的时间
  125.      *
  126.      * @param date 基准日期
  127.      * @param dateField 偏移的粒度大小(小时、天、月等){@link DateField}
  128.      * @param offset 偏移量,正数为向后偏移,负数为向前偏移
  129.      * @return 偏移后的日期
  130.      */
  131.     public static DateTime offset(Date date, DateField dateField, int offset) {
  132.         Calendar cal = Calendar.getInstance();
  133.         cal.setTime(date);
  134.         cal.add(dateField.getValue(), offset);
  135.         return new DateTime(cal.getTime());
  136.     }
  137.     // ------------------------------------ Offset end ----------------------------------------------
复制代码
 

为什么说这么代码比较香呢?你品,你细品!

  • 易读。注意各个方法尤其是以“offset”开头的方法的签名,包括方法名、方法参数,包括javadoc,相当清晰,易读易理解。另外,这几个方法整体来看,像极了我们母语中的排比句。
  • 丰富。按不同的时间单位(如秒、分钟、小时、天、周和月)偏移日期时间值,定义了丰富的方法,各种姿势满足你。
  • 易用。除了offsetMinute/offsetSecond等offsetXxx方法,还提供了yesterday / tomorrow / lastWeek / nextWeek / lastMonth /nextMonth等拿来即用的方法,不必再调用offsetXxx。
  • 简洁。计算时间偏移量的算法是相同的,所以,这些方法内部均调用一个通用的 offset 方法,该方法使用 DateField 枚举值指定要偏移的时间单位和偏移量。
  • 包容。【中途被其他同学打断,一下子断片了。。。】
 
同样,关于本地缓存工具,分享一段我曾经写的LocalCacheUtil工具类。
  1. import cn.hutool.cache.Cache;
  2. import cn.hutool.cache.CacheUtil;
  3. import lombok.extern.slf4j.Slf4j;
  4. import java.util.Collection;
  5. import java.util.concurrent.TimeUnit;
  6. import java.util.function.Supplier;
  7. /**
  8. * 本地缓存工具
  9. */
  10. @Slf4j
  11. public class LocalCacheUtil {
  12.     private static Cache<String, Object> lfuCache = CacheUtil.newLFUCache(256, TimeUnit.MINUTES.toMillis(30));
  13.     private static Cache<String, Object> timedCache = CacheUtil.newTimedCache(TimeUnit.DAYS.toMillis(1));//过期时间给个默认值
  14.     /**
  15.      * 从本地缓存获取数据。如果没有,则设置。(策略:最少使用原则)
  16.      *
  17.      * @param key
  18.      * @param supplier
  19.      * @param <T>
  20.      * @return
  21.      *
  22.      * @see #lfuCache
  23.      */
  24.     public static <T> T getCache(String key, Supplier<T> supplier) {
  25.         return getCache(key, false, supplier);
  26.     }
  27.     /**
  28.      * 从本地缓存获取数据。如果没有,则设置。(策略:最少使用原则)
  29.      *
  30.      * @param key
  31.      * @param cacheNullOrEmpty 是否缓存null或空集合
  32.      * @param supplier
  33.      * @param <T>
  34.      * @return
  35.      *
  36.      * @see #lfuCache
  37.      */
  38.     public static <T> T getCache(String key, boolean cacheNullOrEmpty, Supplier<T> supplier) {
  39.         return getCache(lfuCache, key, null, cacheNullOrEmpty, supplier);
  40.     }
  41.     /**
  42.      * 获取缓存。如果没有,则设置
  43.      *
  44.      * @param key
  45.      * @param seconds
  46.      * @param supplier 缓存数据提供者
  47.      * @param <T>
  48.      * @return
  49.      */
  50.     public static <T> T getCache(String key, long seconds, Supplier<T> supplier) {
  51.         return getCache(key, seconds, false, supplier);
  52.     }
  53.     /**
  54.      * 删除缓存
  55.      *
  56.      * @param key 缓存key
  57.      */
  58.     public static void removeCache(String key) {
  59.         timedCache.remove(key);
  60.     }
  61.     /**
  62.      * 获取缓存。如果没有,则设置
  63.      *
  64.      * @param key
  65.      * @param seconds
  66.      * @param cacheNullOrEmpty 是否缓存null或空集合
  67.      * @param supplier         缓存数据提供者
  68.      * @param <T>
  69.      * @return
  70.      */
  71.     public static <T> T getCache(String key, long seconds, boolean cacheNullOrEmpty, Supplier<T> supplier) {
  72.         return getCache(timedCache, key, seconds, cacheNullOrEmpty, supplier);
  73.     }
  74.     private static <T> T getCache(Cache<String, Object> myCache, String key, Long seconds, boolean cacheNullOrEmpty, Supplier<T> supplier) {
  75.         if (myCache.containsKey(key)) {
  76.             return (T) myCache.get(key);
  77.         } else {
  78.             T result = supplier.get();
  79.             if (!cacheNullOrEmpty) {
  80.                 if (result == null) {
  81.                     log.info("设置缓存---value为null,不设置--- key={}", key);
  82.                     return null;
  83.                 } else if (result instanceof Collection && ((Collection) result).size() == 0) {
  84.                     log.info("设置缓存---value是个空集合,不设置--- key={}", key);
  85.                     return null;
  86.                 }
  87.             }
  88.             log.info("设置缓存 key={}", key);
  89.             if (seconds == null) {
  90.                 myCache.put(key, result);
  91.             } else {
  92.                 myCache.put(key, result, TimeUnit.SECONDS.toMillis(seconds));
  93.             }
  94.             return result;
  95.         }
  96.     }
  97. }
复制代码
View Code 

另外,在这个DateUtil工具类中,有一个弃用的offsetDate方法如下。
  1.     /**
  2.      * 获取指定日期偏移指定时间后的时间
  3.      *
  4.      * @param date 基准日期
  5.      * @param dateField 偏移的粒度大小(小时、天、月等){@link DateField}
  6.      * @param offset 偏移量,正数为向后偏移,负数为向前偏移
  7.      * @return 偏移后的日期
  8.      * @deprecated please use {@link DateUtil#offset(Date, DateField, int)}
  9.      */
  10.     @Deprecated
  11.     public static DateTime offsetDate(Date date, DateField dateField, int offset) {
  12.         return offset(date, dateField, offset);
  13.     }
复制代码
作为一个不断迭代升级的Java工具库,显然hutool不能轻易将之前的方法直接去掉,这会遭到骂娘的。因此,hutool的开发者标记了@Deprecated,并在方法的javadoc里明确指引出来,调用另一个offset(Date, DateField, int)重载。--——————这是一个优秀编码风格,标记弃用,请向使用者描述背景(弃用原因)或告知使用者应该怎么办。
那么,现在,我们来思考一下:为什么弃用这个offsetDate方法改用offset方法呢?欢迎评论区交流!
我在公司内部的软件系统中,一直在践行关于弃用方法的这一优秀编码行为。只是后来,随着开发经验和意识的增强,在行为上做了一些调整。即,我不再一味地标记弃用,而是斩草除根,对于不合理的方法,优秀采用的方式是直接干掉方法并修改对方法的调用,这么做的出发点有三:①我们是中小型内部企业应用系统,工具或组件都是对内使用,具备内部修改的条件;②团队成员编码意识良莠不齐,被明确标记了弃用的方法,有时仍被使用;③最好的方式是一次做好,避免时间一长自己都忘了这些冗余代码了。
 
The End.
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

笑看天下无敌手

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表