设计模式实战-空对象模式,你肯定不知道,但是项目中会常用到到
受 GOF(Gang of Four,四人组)《设计模式》一书的影响,让人误以为设计模式只有 23 种,其实不然,除了《设计模式》介绍的 23 种设计模式外,还有很多经典的设计模式,例如我们本文将要介绍的空对象模式。
1、定义
在空对象模式(Null Object Pattern)中,一个空对象取代 NULL 对象实例的检查。
英文定义如下:
Provide an object as a surrogate for the lack of an object of a given type. The Null Object provides intelligent do nothing behavior, hiding the details from its collaborators.
意思是:为缺少的对象提供一个默认的无意义对象,用来避免 Null 对象的产生。
简单来说,就是用一个空对象,来取代程序中的 Null 值判断,从而让调用者可以直接使用对象,而无需关心对象是否为 Null。
例如,在没用空对象模式之前,要正确的获取以下值:
String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();
它的实现代码如下:
if (user != null) {
Address address = user.getAddress();
if (address != null) {
Country country = address.getCountry();
if (country != null) {
String isocode = country.getIsocode();
if (isocode != null) {
isocode = isocode.toUpperCase();
}
}
}
}
非空判断已经多到令我们崩溃了,如果属性中还有更多的对象,那 Null 值判断就更多了,为了解决这个问题,就要使用本文将要介绍的空对象模式了。
2、组成角色
空对象模式包含如下角色:
- 抽象对象(Abstract Object)角色:声明统一的对象行为(属性和方法);
- 具体对象(Concrete Object)角色:确实存在的具体对象,程序中的非 Null 对象;
- 空对象(Null Object)角色:非具体存在的对象,Null 对象;
- 对象工厂(Object Factory)角色:根据传递的标识得到相关类的工厂类,返回值可以是具体对象或 Null 对象。
角色之间的 UML 关系图如下:
3、观察者模式代码实现
3.1 抽象对象
/**
* 抽象对象
*/
abstract class AbstractObject {
String name;
abstract String getName();
abstract boolean isNull();
}
3.2 具体对象
/**
* 具体对象
*/
class ConcreteObject extends AbstractObject {
public ConcreteObject(final String name) {
this.name = name;
}
@Override
String getName() {
return this.name;
}
@Override
boolean isNull() {
return false;
}
}
3.3 空对象
/**
* 空对象
*/
class NullObject extends AbstractObject {
@Override
String getName() {
return "Not Available in Customer Database";
}
@Override
boolean isNull() {
return true;
}
}
3.4 对象工厂
/**
* 对象生成工厂
*/
class ObjectFactory {
public static AbstractObject creator(final String name) {
AbstractObject result = null;
switch (name) {
case "Java":
result = new ConcreteObject("Java");
break;
case "SQL":
result = new ConcreteObject("SQL");
break;
default:
result = new NullObject();
break;
}
return result;
}
}
程序执行结果如下:
Java Not Available in Customer Database SQL
从以上的代码可以看出,其中 getName () 为所有对象需要执行的公共方法,如果没使用空对象模式的情况下,每次在调用 getName () 之前,我们需要先判空再使用,而如果使用的是空对象模式的话,则可以直接使用(该方法)。
4、优缺点
空对象模式的优点:
- 省去代码中对 Null 值的判断和检查;
- 让代码显的更加优雅和可读性更高;
- 让系统更加稳定,避免程序抛出 NullPointerException 异常。
空对象模式的缺点:
- 因为增加了更多的类信息,从而使系统更复杂。
5、应用场景
JDK 8 中的 Optional 对象使用的就是空对象模式,避免空指针的异常,同时又能写出优雅而简洁的 Java 代码。
Optional 类中有以下几个重要的方法:
- ofNullable () 方法:为指定的值创建一个 Optional, 如果指定的值为 null,则返回一个空的 Optional 对象;
- orElse () 方法:如果有值则将其返回,否则返回指定的其它值;
- map () 方法:如果创建的 Optional 中的值存在,对该值执行提供的 Function 函数调用;
- flagMap () 方法:如果创建的 Optional 中的值存在,就对该值执行提供的 Function 函数调用,返回一个 Optional 类型的值,否则就返回一个空的 Optional 对象。
小贴士:很多人可能对 “对该值执行提供的 Function 函数调用” 这句话不太理解,它的意思是说,例如下面代码: Optional.ofNullable(concreteUser).flatMap(u -> u.getAddress()) 其中 “(u -> u.getAddress ())” 这部分代码就是 “该值执行提供的 Function 函数”。
接下来我们就是用 Optional 对象,优雅的实现判空操作,优雅的实现文章开头 4 层令人崩溃的 Null 值判断,实现代码如下。
5.1 用户类
/**
* 用户类
**/
class User {
public User(Address address) {
this.address = address;
}
private Address address;
public Optional<Address> getAddress() {
return Optional.ofNullable(address);
}
public void setAddress(Address address) {
this.address = address;
}
}
5.2 地址类
/**
* 地址类
**/
class Address {
public Address(Country country) {
this.country = country;
}
private Country country;
public Optional<Country> getCountry() {
return Optional.ofNullable(country);
}
public void setCountry(Country country) {
this.country = country;
}
}
5.3 国际编码类
/**
* 国际编码类
**/
class Country {
public Country(String isocode) {
this.isocode = isocode;
}
private String isocode;
public String getIsocode() {
return isocode;
}
public void setIsocode(String isocode) {
this.isocode = isocode;
}
}
5.4 客户端调用
public class Client {
public static void main(final String[] args) {
// JDK 8 Optional 对象判空示例
// 具体对象
User concreteUser = new User(new Address(new Country("china")));
// 空对象
User nullUser = new User(null);
// 具体对象编码获取
String concreteIsocode = Optional.ofNullable(concreteUser)
.flatMap(u -> u.getAddress())
.flatMap(a -> a.getCountry())
.map(c -> c.getIsocode())
.orElse("暂无").toUpperCase();
// 空对象编码获取
String nullIsocode = Optional.ofNullable(nullUser)
.flatMap(u -> u.getAddress())
.flatMap(a -> a.getCountry())
.map(c -> c.getIsocode())
.orElse("暂无").toUpperCase();
System.out.println("Concrete User:" + concreteIsocode);
System.out.println("Null User:" + nullIsocode);
}
}
程序直接结果如下:
Concrete User:CHINA Null User:暂无
6、使用实例
以生活中场景为例,例如,在一个小商店里,售货员可以根据商品的编码,得到商品的具体名称和价格等信息,实现代码如下。
6.1 抽象商品类
/**
* 抽象商品类
**/
abstract class AbstractGoods {
public String isbn; // 商品编码
public String name;
public Double price;
public abstract void show();
}
6.2 具体商品类
/**
* 具体商品
**/
class ConcreteGoods extends AbstractGoods {
public ConcreteGoods(String isbn, String name, Double price) {
this.isbn = isbn;
this.name = name;
this.price = price;
}
@Override
public void show() {
System.out.println("商品名:" + this.name + ",价格:" + this.price + "元");
}
}
6.3 空商品类
/**
* 空商品类
**/
class NullGoods extends AbstractGoods {
@Override
public void show() {
System.out.println("商品信息暂无!");
}
}
6.4 商品工厂
/**
* 商品查询工厂
**/
class GoodsFactory {
public static AbstractGoods find(final String isbn) {
AbstractGoods result = null;
switch (isbn) {
case "001":
result = new ConcreteGoods("001", "Java面试全解析", 69.0);
break;
case "002":
result = new ConcreteGoods("002", "MySQL面试金典", 19.0);
break;
default:
result = new NullGoods();
break;
}
return result;
}
}
6.5 客户端调用
public class Client {
public static void main(final String[] args) {
final AbstractGoods goods1 = GoodsFactory.find("001");
final AbstractGoods goods2 = GoodsFactory.find("003");
goods1.show();
goods2.show();
}
}
程序执行结果如下:
商品名:Java 面试全解析,价格:69.0 元 商品信息暂无!
7、总结
在 Java 语言中,解决 NullPointerException 异常的常见方法是使用空对象模式,空对象模式可以省去代码中对 Null 值的判断,从而使代码更加的简洁和优雅。在 JDK 8 之后,Java API 给我们提供了 Optional 类,使用它可以优雅且有效的,规避空对象产生 NullPointerException 的问题。
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Linux用if判断目录是否存在实例方法
- CentOS 8.1下搭建LEMP(Linux+Nginx+MySQL+PHP)环境(教程详解)
- Linux echo文本处理命令的使用及示例
- redis妙用-string类型
- redis妙用-hash类型
- redis妙用-list类型
- redis妙用-set类型
- JVM调优实战:解决CMS concurrent-abortable-preclean LongGC的问题
- redis妙用-zset类型
- 【线上排查实战】AOP切面执行顺序你真的了解吗
- 使用markdown,knitr和pandoc在R语言中编写可重现的报告
- R语言广义线性模型(GLMs)算法和零膨胀模型分析
- R语言中广义线性模型(GLM)中的分布和连接函数分析
- R语言自适应平滑样条回归分析
- R语言区间数据回归分析