一道面试题:计算时间偏移量,怎么设计你的程序?
计算时间偏移量,例如,计算当前时间向前偏移 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)
方法,当上面的计算时间偏移量的方法无法满足使用要求时(当然,已经是应有尽有了),你就可以用它了。
同样,关于本地缓存工具,分享一段我曾经编写的基于hutool-cache封装的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; } } }
另外,在这个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)重载。--——————这是一个优秀编码风格,标记弃用,请向使用者描述背景(弃用原因)并告知使用者应该怎么办。(正如阿里巴巴Java开发手册中的【强制】规范:接口过时必须加@Deprecated注解,并清晰地说明采用的新接口或者新服务是什么。)
那么,现在,我们来思考一下:为什么弃用这个offsetDate方法改用offset方法呢?欢迎评论区交流!
我在公司内部的软件系统中,一直在践行关于弃用方法的这一优秀编码行为。只是后来,随着开发经验和意识的增强,在实践过程中做了一些调整。即,我不再一味地标记弃用,而是斩草除根,对于不合理的方法,优秀采用的方式是直接干掉方法并修改对方法的调用,这么做的出发点有三:①我们是中小型内部企业应用系统,工具或组件都是对内使用,具备内部修改的条件;②团队成员编码意识良莠不齐,被明确标记了弃用的方法,有时仍被使用;③最好的方式是一次做好,避免时间一长自己都忘了这些冗余代码了。
The End.
【EOF】欢迎大家关注微信公众号「靠谱的程序员」,让我们一起做靠谱的程序员。
当看到一些不好的代码时,会发现我还算优秀;当看到优秀的代码时,也才意识到持续学习的重要!--buguge
本文来自博客园,转载请注明原文链接:https://www.cnblogs.com/buguge/p/17565583.html