单一职责原则:JAVA LocalDate的设计取舍

学习和理解单一职责,一个比较好的办法,是去看一个真正按这个原则设计的类。比如JDK 17里的java.time.LocalDate。

第一次接触LocalDate时,很容易会认为,既然它负责表示日期,那日期格式化是不是也应该由它来做?但实际上,LocalDate根本不负责格式化。

LocalDate是一个public final class,核心字段只有year、month、day三个字段,而且都是final。至于日期应该怎么显示,则交给了另一个类DateTimeFormatter。

为什么要这么干?

因为日期是什么 和日期怎么显示,本来就是两件不同的事。

这其实就是单一职责原则:一个类应该只有一个引起它变化的原因。

LocalDate关心的是日期本身,比如是哪一年、哪一月、哪一天,以及日期之间怎么计算。这些东西和日历规则有关,通常不会频繁变化。

DateTimeFormatter关心的则是怎么把这个日期展示出来,它根据需求不断的变化,但是LocalDate不需要跟着改。

这就是单一职责真正解决的问题:让变化发生在该发生的地方。

如果把格式化也塞进LocalDate,以后每增加一种显示方式,都要修改这个类。而拆开之后,日期计算和日期展示各自维护,改一边不会影响另一边。

看起来是LocalDate在格式化,其实不是

看格式化的实际写法:

java 复制代码
// 日期对象和格式规则是两个对象
LocalDate date = LocalDate.now();
String text = date.format(DateTimeFormatter.ofPattern("M月d日"));

表面上看,调用的是LocalDate的format()方法,好像格式化这件事是LocalDate自己负责的。

但翻一下它的实现就知道,format()本身并不做格式化。它只是把LocalDate和DateTimeFormatter交给后者处理,真正按照格式规则拼出字符串的,是DateTimeFormatter。

所以这里其实是两个对象各管一件事:

  • LocalDate:保存日期,并负责日期相关的计算。
  • DateTimeFormatter:负责把日期转换成指定格式的字符串。

LocalDate提供format(),只是把格式化这个操作交给DateTimeFormatter,自己并不处理具体的格式化逻辑。

这就是单一职责在实际代码里的样子:一个类负责自己的事情,另一件事交给专门的类。

读到这里,你可能还是有疑问,不应该提供format()方法呀,其实不是这么理解的,这个从API的易用性角度来考虑的,它就是一种语法糖,底层依然严格遵守了单一职责的。

老版本Date的教训

java.util.Date是跟着JDK 1.0一起出现的,最早的设计比较简单,但随着时间API不断增加,它慢慢承担了越来越多的事情:表示日期、修改日期、处理时区,还提供toLocaleString()这种直接返回格式化字符串的方法。

问题也就慢慢出来了。

日期本身怎么表示,和日期怎么转换、怎么显示,本来就是不同的变化方向。它们全放在Date里,时间一长,这个类自然会越来越难维护。

所以今天再看Date的源码,会发现不少方法都已经标记了@Deprecated。像getYear()需要自己加上1900,月份从0开始计算,很多接口的语义也已经不符合现在的使用方式。

后来JDK引入了java.time,把日期、时间、时区、格式化等职责重新拆开,LocalDate、LocalDateTime、ZonedDateTime、DateTimeFormatter等类各自负责不同的事情。

当然,Date不能简单当成一个反面教材。它诞生于1996年,当时的设计背景和今天完全不同。

真正值得留下来的,是它后来暴露出来的问题:变化原因不同的事情,尽量不要塞进同一个类。

顺便说一句,除了职责混杂,Date的可变性也让它在多线程环境下危机四伏。java.time在设计时不仅拆分了职责,还全面拥抱了不可变设计,这两者往往是相辅相成的。

怎么判断一个类承担的责任太多了

日常在写业务代码的时候,有个办法可以检验。下次需求变更时留意一下:这次改动迫使哪些类发生了修改。两件不相干的事,比如日期怎么算和日期怎么显示,总在逼着你改同一个类,说明这个类管太多事情了,该分成两个。

小结

单一职责经常被理解成类要写得小,这是个常见误解。它约束的不是体积,是变更原因。

相关推荐
现任明教教主~5 分钟前
SpringBoot+Vue学生宿舍管理系统源码 带部署文档
vue.js·spring boot·后端
panxianren5 小时前
Bun 1.4 的 bun test --parallel 将 4 秒的测试套件缩短到约 1 秒
java·javascript
码事漫谈7 小时前
骂AI,会让它做得更好吗?
后端
sunshine22 girl7 小时前
Java学习七 Java 项目实战2-注册业务
java·学习
陈随易7 小时前
bm2,Node.js 与 Bun 项目部署新选择
前端·后端·程序员
省钱兄--zs7 小时前
北京24小时自助健身房系统软件开发实战:从架构设计到部署指南
java·数据仓库·spring boot·小程序·需求分析
youdexiang8 小时前
Android录音软件时间关键词检索功能分析
java·人工智能
架构技术专栏8 小时前
AI Agent 框架怎么选:从一次制度查询拆出技术边界
后端·面试
sunshine22 girl8 小时前
Java学习七 Java 项目实战1-项目创建和主页面搭建
java·开发语言·学习
code2cat9 小时前
Java进阶篇之AtomicStampedReference:引用回到原值,版本戳仍能记住变化
java·cas·并发编程·aba