小对象,大作用,好优雅!
(笔者将以Java语言为例)
一:问题初现
在工作中,笔者发现,开发同学们或多或少都会遇到这些问题:
针对同种功能的字段,需要重复提供边界校验,甚至干脆不校验。很多常见的解决方式是这样的:校验用注解直接写在接受前端参数的“VO对象”里。
public class UserVO {
@NotBlank(message = "name不能为空")
private String name;
@NotBlank(message = "描述不能为空")
private String description;
}
太不优雅,太不体面了!
作为追求工程卓越的精致打工人,就该高效优雅、又快又好地实现功能,然后合理的摸鱼。
就在笔者苦思冥想之际,突然一到灵光击中了脑壳,这不就是《重构》理论中的霰弹式修改吗!?
含义:
牵一发而动全身,每次遇到某种变化,都必须在不同的类中做出小修改
坏处:
代码散步各处,不利于扩展和阅读,增加代码修改难度及工作量
目标:
尽量使某类变化通过某个特定类来处理,避免修改过多类。提升代码可扩展性,减少不必要的工作量。《重构》 ,关于霰弹式修改
于是,修修改改、一番折腾之后,小对象就诞生了:
public class Name {
private String value;
}
public class Description {
private String value;
}
二:“初见”小对象
面向对象九式中早有提及:“戒条三:封装所有的原生类型和字符串”。
基本类型的表达能力是非常有限的,
public class User {
private String id;
private String name;
/**
密码
**/
private String pd;
}
如果不看字段的名字,谁也不知道这个String字符串是干啥的,它只有作为通用字符串的标准行为。如果字段命名再不妥当一点,就得再加注释去弥补它、解释它,属实是有一点“叠床架屋”了。就例如这个pd。
有一句话说得好:“ 伟大的代码可以被一年级CS学生轻松读懂 ”,我们不谈伟大,好的代码也应该保证其相应的易读性。
大家都清楚,字段的命名很重要,好的命名是成功的一半,但同样的,其类型也同样重要。类型是对字段最强有力的解释,它可以生动的表达这个字段的意义。这样就可以不需要用注解去“欲盖弥彰”。
当我们把一个实体的所有字段全部用小对象封装起来后,这个对象就生动了起来,活生生地站在了我们面前。
public class User {
private Id id;
private Name name;
private Password password;
}
public class Id {
private String value;
}
public class Name {
private String value;
}
public class Password {
private String value;
}
三:小对象里的天地
小对象往往是面向对象的隐喻。开发者需要更专注于实体与其属性的状态与行为,而不是简单的纯数据处理——后者是过程化的思考。
小对象以及由小对象构成的实体、聚合,可以真实地对真实世界模型做很好的映射。
但是小对象里可不只是单纯的对基本类型的封装。一般整个业务系统里,名称的长度限制和编码的长度限制可能是不同的,但所有的名称的长度限制都是相同的,所有的编码的长度限制都是相同的。
所以,回到一开始的问题,倘若我们把字段的长度限制做到小对象里去呢?
public class Name {
private String value;
public Name(String value) {
this.value = value;
verify();
}
private void verify() {
if (StringUtils.isEmpty(value) || value.length > 10) {
throw new IllegalArgumentException("名字不能大于10个字符。");
}
}
}
public class Password {
private String value;
public Password(String value) {
this.value = value;
verify();
// encoding,略
}
private void verify() {
if (StringUtils.isEmpty(value) || value.length > 16) {
throw new IllegalArgumentException("密码不能超过16个字符。");
}
}
}
是不是问题就迎刃而解了?很直观,很优雅。
而且,它还可以顺带解决另一个问题;避免Utils的滥用。例如系统中有名字的概念,所有name字段都可以被输出成"姓 + 名"或"名 + 姓"两种方式,这种业务就可能会带来NameUtils的使用。但这个模型就很“贫血”。为什么Name自己不能按照格式输出,而是非要一个Utils帮忙做输出呢?Utils在系统中应该少用,最好不用。
如果有了个Name的小对象,Name自己就可以做到这个行为了,赞!
四:近而深究其意义
小对象可以组成对真实模型的隐喻,可以让实体更生动、活泼,更还有很多意义存在。
笔者试图总结了一下:
1. 它践行了分离关注的思想,把边界、异常处理的责任放在对应的业务对象里,把工作的重心放在需要交付的业务逻辑上。
2. 业务代码更集中,不会分散到controller(web层)和前端视图对象中。
3. 代码整洁干净,不会有一堆注解
4. 不依赖于框架(例如spring-validation)。团队升级框架或随时移除框架时不会影响任何业务代码。而使用框架可能会导致业务代码流失的情况
5. 提高交付速率
笔者也不是纸上谈兵、夸夸其谈,笔者也和团队伙伴在真实项目中实践,并证明了其卓有成效。这个理念已经融合进了瀚城软件研发的开源企业应用级框架里了,感兴趣的小伙伴可以clone下来看看????
https://github.com/highsoft-shanghai/hare
(基于Java+SpringBoot的实践)