前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >细节之处见真章 - 请求对象 trim 最佳实践

细节之处见真章 - 请求对象 trim 最佳实践

作者头像
明明如月学长
发布2023-03-15 09:45:22
3540
发布2023-03-15 09:45:22
举报

一、背景

日常开发中,经常需要对前端传入的请求对象(如 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 更简洁。

代码语言:javascript
复制
    /**
     * <p>Removes control characters (char &lt;= 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 &lt;= 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 &lt;= 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 不需要显示处理,会更加清爽。

三、启发

细节之处见真章,代码的功底并不都体现在大的地方,日常开发中自己觉得“别扭”的地方,都可以停下来斟酌优化一番。

大家在思考代码优化方案时,主要遵循软件设计的常见原则,如高内聚、弱耦合、降低复杂度的原则。

高内聚弱耦合.png
高内聚弱耦合.png

重点参考设计模式的几大原则。 • 单一职责原则 (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 导致遗漏后者逻辑重复的问题,本文给出推荐的方案,希望对大家有帮助。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-03-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、背景
  • 二、方案
    • 2.1 入口处 trim 后设置回去
      • 2.2 将 trim 逻辑封装在请求对象内部
      • 三、启发
      • 四、总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档