设计模式第九讲:常见重构技巧 - 去除不必要的!=

设计模式第九讲:常见重构技巧 - 去除不必要的!=

项目中会存在大量判空代码,多么丑陋繁冗!如何避免这种情况?我们是否滥用了判空呢?本文是设计模式第九讲,讲解常见重构技巧:去除不必要的!=

文章目录

  • 设计模式第九讲:常见重构技巧 - 去除不必要的!=
    • 1、场景一:null无意义之常规判断空
    • 2、场景二:null无意义之使用断言Assert
    • 3、场景三:写util类是否都需要逐级判断空?
    • 4、场景四:让null变的有意义
    • 5、场景五:Java8中使用Optional(推荐)

1、场景一:null无意义之常规判断空

  • 通常是这样的
private void xxxMethod(String key){
    if(key != null && !"".equals(key)){
        // do something
    }
}
  • 初步的,使用Apache Commons,Guvava,Hutool等 StringUtils
private void xxxMethod(String key){
    if(StringUtils.isNotEmpty(key)){
        // do something
    }
}

2、场景二:null无意义之使用断言Assert

  • 考虑用Assert断言
private void xxxMethod(String key){
    Assert.notNull(key);

    // do something
}

3、场景三:写util类是否都需要逐级判断空?

逐级判断空,还是抛出自定义异常,还是不处理?It Depends…

hutool IdcardUtil 显然是交给调用者判断的。

/**
    * 是否有效身份证号
    *
    * @param idCard 身份证号,支持18位、15位和港澳台的10位
    * @return 是否有效
    */
public static boolean isValidCard(String idCard) {
    idCard = idCard.trim();// 这里idCard没判断空
    int length = idCard.length();
    switch (length) {
        case 18:// 18位身份证
            return isValidCard18(idCard);
        case 15:// 15位身份证
            return isValidCard15(idCard);
        case 10: {// 10位身份证,港澳台地区
            String[] cardVal = isValidCard10(idCard);
            return null != cardVal && "true".equals(cardVal[2]);
        }
        default:
            return false;
    }
}
  • 再比如 Apache Common IO中, 并没判断空
/**
    * Copy bytes from a <code>byte[]</code> to an <code>OutputStream</code>.
    * @param input the byte array to read from
    * @param output the <code>OutputStream</code> to write to
    * @throws IOException In case of an I/O problem
    */
public static void copy(final byte[] input, final OutputStream output)
        throws IOException {
    output.write(input);
}

4、场景四:让null变的有意义

返回一个空对象(而非null对象),比如NO_ACTION是特殊的Action,那么我们就定义一个ACTION。下面举个例子,假设有如下代码

public interface Action {
  	void doSomething();
}

public interface Parser {
  	Action findAction(String userInput);
}

其中,Parse有一个接口FindAction,这个接口会依据用户的输入,找到并执行对应的动作。假如用户输入不对,可能就找不到对应的动作(Action),因此findAction就会返回null,接下来action调用doSomething方法时,就会出现空指针。

解决这个问题的一个方式,就是使用 Null Object pattern(空对象模式)

NullObject模式首次发表在“ 程序设计模式语言 ”系列丛书中。一般的,在面向对象语言中,对对象的调用前需要使用判空检查,来判断这些对象是否为空,因为在空引用上无法调用所需方法。

img

我们来改造一下

类定义如下,这样定义findAction方法后,确保无论用户输入什么,都不会返回null对象:

public class MyParser implements Parser {
    private static Action NO_ACTION = new Action() {
      	public void doSomething() { /* do nothing */ }
    };

    public Action findAction(String userInput) {
        // ...
        if ( /* we can't find any actions */ ) {
          	return NO_ACTION;
        }
    }
}

对比下面两份调用实例

1.冗余: 每获取一个对象,就判一次空

Parser parser = ParserFactory.getParser();
if (parser == null) {
  	// now what?
  	// this would be an example of where null isn't (or shouldn't be) a valid response
}
Action action = parser.findAction(someInput);
if (action == null) {
  	// do nothing} 
else {
  	action.doSomething();
}

2.精简

ParserFactory.getParser().findAction(someInput).doSomething();

因为无论什么情况,都不会返回空对象,因此通过findAction拿到action后,可以放心地调用action的方法。

顺便再提下一个插件:

.NR Null Object插件 NR Null Object是一款适用于Android Studio、IntelliJ IDEA、PhpStorm、WebStorm、PyCharm、RubyMine、AppCode、CLion、GoLand、DataGrip等IDEA的Intellij插件。其可以根据现有对象,便捷快速生成其空对象模式需要的组成成分,其包含功能如下:

  • 分析所选类可声明为接口的方法;
  • 抽象出公有接口;
  • 创建空对象,自动实现公有接口;
  • 对部分函数进行可为空声明;
  • 可追加函数进行再次生成;
  • 自动的函数命名规范

5、场景五:Java8中使用Optional(推荐)

假设我们有一个像这样的类层次结构:

class Outer {
    Nested nested;
    Nested getNested() {
        return nested;
    }
}
class Nested {
    Inner inner;
    Inner getInner() {
        return inner;
    }
}
class Inner {
    String foo;
    String getFoo() {
        return foo;
    }
}

解决这种结构的深层嵌套路径是有点麻烦的。我们必须编写一堆 null 检查来确保不会导致一个 NullPointerException:

Outer outer = new Outer();
if (outer != null && outer.nested != null && outer.nested.inner != null) {
    System.out.println(outer.nested.inner.foo);
}

我们可以通过利用 Java 8 的 Optional 类型来摆脱所有这些 null 检查。map 方法接收一个 Function 类型的 lambda 表达式,并自动将每个 function 的结果包装成一个 Optional 对象。这使我们能够在一行中进行多个 map 操作。Null 检查是在底层自动处理的。

Optional.of(new Outer())
    .map(Outer::getNested)
    .map(Nested::getInner)
    .map(Inner::getFoo)
    .ifPresent(System.out::println);

还有一种实现相同作用的方式就是通过利用一个 supplier 函数来解决嵌套路径的问题:

Outer obj = new Outer();
resolve(() -> obj.getNested().getInner().getFoo())
    .ifPresent(System.out::println);

调用 obj.getNested().getInner().getFoo()) 可能会抛出一个 NullPointerException 异常。在这种情况下,该异常将会被捕获,而该方法会返回 Optional.empty()。

public static <T> Optional<T> resolve(Supplier<T> resolver) {
    try {
        T result = resolver.get();
        return Optional.ofNullable(result);
    }
    catch (NullPointerException e) {
        return Optional.empty();
    }
}

请记住,这两个解决方案可能没有传统 null 检查那么高的性能。不过在大多数情况下不会有太大问题。

  • 更多Optional,可以看这篇: Java8特性第三讲:如何使用Optional类优雅解决业务npe问题
    • Optional类的意义
    • Optional类有哪些常用的方法
    • Optional举例贯穿所有知识点
    • 多重类嵌套Null值判断

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/101367.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot整合websockt实现消息对话

文章目录 前言websockt什么是websockt&#xff1f;websockt和Socket区别代码部分实战应用 前言 websockt 什么是websockt&#xff1f; WebSocket是一种在Web应用程序中实现实时双向通信的技术。Web应用程序通常是基于HTTP协议的&#xff0c;HTTP是一种请求/响应式的协议&…

基于Gin框架的HTTP接口限速实践

在当今的微服务架构和RESTful API主导的时代&#xff0c;HTTP接口在各个业务模块之间扮演着重要的角色。随着业务规模的不断扩大&#xff0c;接口的访问频率和负载也随之增加。为了确保系统的稳定性和性能&#xff0c;接口限速成了一个重要的话题。 1 接口限速的使用场景 接口…

简单了解ICMP协议

目录 一、什么是ICMP协议&#xff1f; 二、ICMP如何工作&#xff1f; 三、ICMP报文格式 四、ICMP的作用 五、ICMP的典型应用 5.1 Ping程序 5.2 Tracert(Traceroute)路径追踪程序 一、什么是ICMP协议&#xff1f; ICMP因特网控制报文协议是一个差错报告机制&#xff0c;…

树的介绍(C语言版)

前言 在数据结构中树是一种很重要的数据结构&#xff0c;很多其他的数据结构和算法都是通过树衍生出来的&#xff0c;比如&#xff1a;堆&#xff0c;AVL树&#xff0c;红黑色等本质上都是一棵树&#xff0c;他们只是树的一种特殊结构&#xff0c;还有其他比如linux系统的文件系…

SpringBoot 整合 RabbitMQ

1. 创建 SpringBoot 工程 把版本改为 2.7.14 引入这两个依赖: <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency><dependency><groupId>org.springfr…

超图嵌入论文阅读1:对偶机制非均匀超网络嵌入

超图嵌入论文阅读1&#xff1a;对偶机制非均匀超网络嵌入 原文&#xff1a;Nonuniform Hyper-Network Embedding with Dual Mechanism ——TOIS&#xff08;一区 CCF-A&#xff09; 背景 超边&#xff1a;每条边可以连接不确定数量的顶点 我们关注超网络的两个属性&#xff1…

leetcode 941. 有效的山脉数组

2023.9.2 可以用双指针法来做&#xff0c;left指向数组起点&#xff0c;right指向数组终点&#xff0c;left满足条件则左移&#xff0c;right满足条件则右移&#xff0c;最终两指针重合则返回true。 期间任一条件不满足则返回false。 代码如下&#xff1a; class Solution { p…

【小沐学Unity3d】3ds Max 多维子材质编辑(Multi/Sub-object)

文章目录 1、简介2、精简材质编辑器2.1 先创建多维子材质&#xff0c;后指定它2.2 先指定标准材质&#xff0c;后自动创建多维子材质 3、Slate材质编辑器3.1 编辑器简介3.2 编辑器使用 结语 1、简介 多维子材质&#xff08;Multi/Sub-object&#xff09;是为一个模形&#xff0…

JavaScript基础语法03——JS注释、结束符

哈喽&#xff0c;大家好&#xff0c;我是雷工&#xff01; 今天继续学习JavaScript基础语法知识&#xff0c;注释和结束符&#xff0c;以下为学习笔记。 一、JavaScript注释 JavaScript注释有什么作用&#xff1f; JavaScript注释可以提高代码的可读性&#xff0c;能够帮助像…

深入了解Docker镜像操作

Docker是一种流行的容器化平台&#xff0c;它允许开发者将应用程序及其依赖项打包成容器&#xff0c;以便在不同环境中轻松部署和运行。在Docker中&#xff0c;镜像是构建容器的基础&#xff0c;有些家人们可能在服务器上对docker镜像的操作命令不是很熟悉&#xff0c;本文将深…

微信小程序手机号快速验证组件调用方式

目录 一、测试环境 二、问题现象 三、总结 手机号验证组件&#xff08;包括快速验证组件和实时验证组件&#xff09;调用后无法对事件进行回调这个问题&#xff0c;先说结论&#xff0c;以下是正确的使用方式&#xff1a; <!-- 手机号快速验证组件 --> <button op…

人员位置管理,点亮矿山安全之路

矿山作为一个高危行业&#xff0c;安全问题一直备受关注。人员定位置管理是现代矿山安全管理的重要一环&#xff0c;可以帮助企业更好地实现对人员的实时监控和管理。因此&#xff0c;矿山人员位置管理系统对于矿山安全生产和管理非常重要&#xff0c;可以帮助减少安全事故的发…

从零开发JavaWeb入门项目--十天掌握

原文网址&#xff1a;从零开发JavaWeb入门项目--十天掌握_IT利刃出鞘的博客-CSDN博客 简介 这是一个靠谱的JavaWeb入门项目实战&#xff0c;名字叫蚂蚁爱购。从零开发项目&#xff0c;视频加文档&#xff0c;十天就能学会开发JavaWeb项目&#xff0c;教程路线是&#xff1a;搭…

你知道用Woof创建的Linux吗?

Quirky 8.2 已发布&#xff0c;它是 Puppy Linux 的姊妹项目&#xff0c;是用一份叫 Woof 的定制工具创建的 Linux 发行。 新版本 Quirky 8.2 运行在 64 位的 x86 计算机上&#xff0c;主要提供了针对以前的 8.x 版本的增量改进。 Quirky Linux 8.2 x86_64 的代号是Xerus&…

【ES6】Proxy的高级用法,实现一个生成各种 DOM 节点的通用函数dom

下面的例子则是利用get拦截&#xff0c;实现一个生成各种 DOM 节点的通用函数dom。 <body> </body><script>const dom new Proxy({}, {get(target, property) {return function(attrs {}, ...children) {const el document.createElement(property);for …

PYTHON知识点学习-函数(下)

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由 Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…

每日一题 1372二叉树中的最长交错路径

题目 给你一棵以 root 为根的二叉树&#xff0c;二叉树中的交错路径定义如下&#xff1a; 选择二叉树中 任意 节点和一个方向&#xff08;左或者右&#xff09;。如果前进方向为右&#xff0c;那么移动到当前节点的的右子节点&#xff0c;否则移动到它的左子节点。改变前进方…

第9章 函数

本章介绍以下内容&#xff1a; 关键字&#xff1a;return 运算符&#xff1a;*&#xff08;一元&#xff09;、&&#xff08;一元&#xff09; 函数及其定义方式 如何使用参数和返回值 如何把指针变量用作函数参数 函数类型 ANSI C原型 递归 如何组织程序&#xff1f;C的设…

2023年的深度学习入门指南(26) - 在自己电脑上运行通义千问7b模型

2023年的深度学习入门指南(26) - 在自己电脑上运行通义千问7b模型 通过量化&#xff0c;通义千问4位量化的模型大小为5.86G&#xff0c;可以在3060等小于16G的家用GPU上也可以运行起来。 通义千问7b的量化运行 通义千问7b提供了4位量化好的Qwen/Qwen-7B-Chat-Int4模型&#…

kaggle赛后总结

1. 宽表 2.缺失值的处理方法 最简单粗暴的就是删除&#xff0c;这种情况是凡是有缺失值行数很少。均值替代。缺失值的行数比较多一点儿的时候&#xff0c;直接删除会影响样本数量&#xff0c;那就均值替代&#xff0c;或者中位数替代等方法。还有复杂的方法&#xff0c;把有缺…