一、背景
日常开发中,经常需要对前端传入的请求对象(如 StudentQueryVO)的某些属性执行 trim 操作,比如搜索的关键字、输入的名称等。 很多人会选择在用到的时候,对其中的属性执行 trim 操作然后再使用。这样做很容易出现: (1)有些用到的地方想起来执行了 trim,有些地方没有 trim,很难保持一致。 (2)不同的地方需要执行相似的 trim 操作,代码复用性不高。
二、方案
2.1 入口处 trim 后设置回去
我们可以选择在 Facade / Controller 层,取出对应的属性,执行 trim 操作,然后赋值回去。 这样不管内部执行了多少次转换,不管多少次使用这些待 trim 的属性,都不会忘记处理,也都不需要重复处理。
代码语言:javascript复制public PageResult<Student> queryStudents(StudentQuery query){
//1 执行 trim
String name = query.getName();
if (name != null && !name.isEmpty()) {
query.setName(name.trim());
}
String nickname = query.getNickname();
if (nickname != null && !nickname.isEmpty()) {
query.setNickname(nickname.trim());
}
//2 执行查询
studentService.pageQuery(query);
}
这样写虽然可以解决问题,但是不太优雅。
2.2 将 trim 逻辑封装在请求对象内部
我们可以对上述方案再一次优化。 可以在构造查询对象时自动执行 trim 方法,也可以在外部执行一次 trim 方法即可。 这样将 trim 的逻辑封装在查询对象内部,可以尽可能降低耦合,后面如果新增属性也需要 trim ,直接在 trim 方法里处理掉即可,外部也不需要感知。
代码语言:javascript复制package org.example.demo;
// 定义一个学生查询类
public class StudentQuery {
// 定义学生的学号、姓名、昵称属性
private String id; // 学号
private String name; // 姓名
private String nickname; // 昵称
public StudentQuery() {
}
// 定义构造方法,用于创建学生查询对象
public StudentQuery(String id, String name, String nickname) {
this.id = id;
this.name = name;
this.nickname = nickname;
// 自动 trim
trim();
}
// 定义获取和设置属性的方法
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
// 定义一个 trim 方法,用于去除属性的空格
public void trim() {
// 调用 trim 方法赋值给 name 属性
name = StringUtils.trimToNull(name);
// 调用 trim 方法赋值给 nickname 属性
nickname = StringUtils.trimToNull(nickname);
}
}
底层建议使用 org.apache.commons.lang3.StringUtils#trimToNull
可以让 trim 更简洁。
/**
* <p>Removes control characters (char <= 32) from both
* ends of this String returning {@code null} if the String is
* empty ("") after the trim or if it is {@code null}.
*
* <p>The String is trimmed using {@link String#trim()}.
* Trim removes start and end characters <= 32.
* To strip whitespace use {@link #stripToNull(String)}.</p>
*
* <pre>
* StringUtils.trimToNull(null) = null
* StringUtils.trimToNull("") = null
* StringUtils.trimToNull(" ") = null
* StringUtils.trimToNull("abc") = "abc"
* StringUtils.trimToNull(" abc ") = "abc"
* </pre>
*
* @param str the String to be trimmed, may be null
* @return the trimmed String,
* {@code null} if only chars <= 32, empty or null String input
* @since 2.0
*/
public static String trimToNull(final String str) {
final String ts = trim(str);
return isEmpty(ts) ? null : ts;
}
下面写个简单的单测验证下:
代码语言:javascript复制import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class StudentQueryTest {
// 直接使用全参构造方法
@Test
public void testStudentQueryAllArgs() {
// 构造一个 StudentQuery 对象
StudentQuery sq = new StudentQuery("001", " Alice ", " Bob ");
// 断言属性的值是否符合预期
assertEquals("001", sq.getId());
assertEquals("Alice", sq.getName());
assertEquals("Bob", sq.getNickname());
}
// 使用午无参构造方法
@Test
public void testStudentQueryNoArgs() {
// 构造一个 StudentQuery 对象
StudentQuery sq = new StudentQuery();
// 调用 set 方法给出一些随机值
sq.setId("002");
sq.setName(" Charlie");
sq.setNickname(" David ");
// 调用 trim 方法去除空格
sq.trim();
// 断言属性的值是否符合预期
assertEquals("002", sq.getId());
assertEquals("Charlie", sq.getName());
assertEquals("David", sq.getNickname());
}
}
上面的请求就可以简化为:
代码语言:javascript复制public PageResult<Student> queryStudents(StudentQuery query){
//1 执行 trim
query.trim();
//2 执行查询
studentService.pageQuery(query);
}
如果是服务内部调用,构造 StudentQuery 时使用全参构造方法,就可以自动调用 trim 不需要显示处理,会更加清爽。
三、启发
细节之处见真章,代码的功底并不都体现在大的地方,日常开发中自己觉得“别扭”的地方,都可以停下来斟酌优化一番。
大家在思考代码优化方案时,主要遵循软件设计的常见原则,如高内聚、弱耦合、降低复杂度的原则。
重点参考设计模式的几大原则。 • 单一职责原则 (Single Responsibility Principle):一个类或者一个方法只负责一项职责,避免过多的功能耦合在一起。 • 开放-关闭原则 (Open-Closed Principle):一个软件实体应该对扩展开放,对修改关闭,即在不改变原有代码的基础上增加新的功能。 • 里氏替换原则 (Liskov Substitution Principle):子类应该能够完全替代父类,并且保持程序的正确性和稳定性,避免子类违背父类的约定。 • 依赖倒转原则 (Dependence Inversion Principle):高层模块不应该依赖于低层模块,二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象,即要针对接口编程,而不是针对实现编程12。 • 接口隔离原则 (Interface Segregation Principle):一个接口应该尽可能小,只包含客户端需要的方法,避免出现臃肿的接口。 • 迪米特法则(Law Of Demeter),又叫“最少知道法则”:一个对象应该尽可能少地与其他对象发生相互作用,只与直接相关的对象通信,降低对象之间的耦合度。 • 组合/聚合复用原则 (Composite/Aggregate Reuse Principle):在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新对象通过向这些对象的委派达到复用已有功能的目的,优先使用组合或者聚合关系复用代码,而不是使用继承。
本文重点采用了迪米特法则来讲 trim 的逻辑封装在请求对象内部,避免 trim 的逻辑外溢,对使用者非常友好。
四、总结
本文讲解的内容虽然比较简答,但是实际开发中很多同学采用到处 trim 导致遗漏后者逻辑重复的问题,本文给出推荐的方案,希望对大家有帮助。