基于图书管理场景对空对象模式的深入认识

2020-10-21 03:50汤致轩姚圣扬
科技创新与应用 2020年30期
关键词:设计模式软件工程

汤致轩 姚圣扬

摘  要:在Java程序设计中,经常会遇到java.lang.NullPointerException空指针异常,在客户端总是需要判断某一对象是否为空对象,这使得客户端掌握了主动权,且程序不够友好。文章从这些常见的问题出发,分析了一个在经典的23种软件设计模式以外的模式——空对象模式,通过图书管理的场景引出问题,并合理地运用空对象模式对问题进行分析和解决,讨论了空对象模式的实现方式及应用场景,阐述了对空对象模式的优缺点分析与对软件设计模式的深入理解。

关键词:空对象模式;设计模式;特殊对象;软件工程;Java

中图分类号:TP311.52      文献标志码:A         文章编号:2095-2945(2020)30-0001-05

Abstract: In Java programming, we often encounter java.lang.NullPointerException, and in the client end, there is always need to judge whether an object is a null object, which makes the client master grasp the initiative, and the program is not friendly enough. From these common problems, this paper analyzes a pattern other than the classic 23 software design patterns-null object pattern. Through the scene of book management, some problems are pointed out. Therefore, this paper reasonably uses the null object pattern to analyze and solve the problems, discusses the realization and application scenarios of the null object pattern, and expounds the advantages and disadvantages of the null object pattern and the deep understanding of the software design pattern.

Keywords: null object pattern; design patterns; special objects; software engineering; Java

软件设计模式是一种为多数人知道、能被反复使用、并分类编目的代码设计经验的概括与总结。GoF所著《Design Patterns: Elements of Reusable Object-Oriented Software》是设计模式方面的经典之作,其中介绍了23种设计模式,但是设计模式并不止这23种,还有很多实用的软件设计模式。本文从这23种模式以外的一种设计模式——空对象模式出发,通过应用实例对空对象模式进行分析,深入挖掘此模式,阐述对此模式的理解。

在Java程序设计的过程中,经常遇到抛出空指针异常的报错信息,本文基于这种常见的问题,引入一种特殊的对象——空对象来避免这样的问题。通过使用空对象模式,对一个简单的图书管理场景进行设计,使得程序更加友好和简洁,并阐述空对象模式的实现方式及应用场景,总结空对象模式的优缺点分析与深入认识。

1 空对象模式

1.1 模式动机

在Java程序设计的过程中,经常遇到调用空对象的方法抛出空指针异常的报错信息,以及在客户端经常需要多次使用if语句检查某一对象是否为空而导致非常多的冗余的检查代码,并且,在客户端的判断操作与动作使得主动权交给了客户端,而不是系统本身,造成了程序设计缺乏优雅与健壮性。

因此,引入空对象模式来更好地解决上述问题,使我们的代码更加优雅,程序更加健壮。

1.2 模式定义

按照目的来分,设计模式可以分为创建型模式、结构型模式和行为型模式。

创建型模式用来处理对象的创建过程;结构型模式用来处理类或者对象的组合;行为型模式用来对类或对象怎样交互和怎样分配职责进行描述。空对象模式所属的类别及定义如下:

1.2.1 模式分类

空对象模式属于三种设计模式中行为型模式的一种,也就是说模式特别关注对象之间的通信,如果对象的行为设计的好,那么对象的行为就会更清晰,它们之间的协作效率就会提高。可以在进行对象间交流活动时增强弹性。

空对象模式引出空对象,它可以接收传递给它的所代表对象的信息,但是将返回表示为实际上并不存在任何“真实”的对象的值,是反应一个不做任何动作的关系。通过这种方式,可以假设所有的对象都是有效的,而不必浪费巨大精力去检查null。

1.2.2 模式定義

在空对象模式(Null Object Pattern)中,一个空对象取代NULL对象实例的检查。Null对象不是检查空值,而是反应一个不做任何动作的关系。这样的Null对象也可以在数据不可用的时候提供默认的行为。

空对象模式是一种提供智能的,不作为的行为,从它的合作者中隐藏细节。

由模式的定义我们可知,空对象实际上是为解决经常抛出的空指针异常错误和代码设计不合理问题而设计的一种特殊的对象。

1.3 类图及实现

空对象模式提出一个空对象来代替null,防止空指针报错对整个程序甚至整个系统的运行影响,获取对空对象的控制,使系统较为稳定的运行,以下是一个典型的空对象模式的类图及实现。

1.3.1 类图

典型空对象模式的类图如图1所示。

在图中我们可以分析:空对象模式拥有一个抽象类,每种具体的类和一个空对象类与抽象类的关系是继承关系,它们继承此抽象类,并且实现其中的抽象方法;客户端与抽象类之间为依赖关系,客户端依赖于抽象类。

1.3.2 实现

针对上面的空对象模式的类图,我们可以编写如下代码,来模拟空对象模式的实现。

首先是抽象类AbstractOperation,其中包括抽象方法request(),所有继承它的子类都需要实现此方法。代码如下:

public abstract class AbstractOperation

{

abstract void request();

}

RealOperation类为具体的类,继承抽象类AbstractOperation,并且我们简单地实现其中的request方法:

Public class RealOperation extends

AbstractOperation {

@Override

void request() {

System.out.println("do something");

}

}

NullOperation类则是本文所要重点介绍的——空对象类,此类继承抽象类AbstractOperation,并且我们简单地实现其中的request方法:

public class NullOperation extends

AbstractOperation{

@Override

void request() {

// do nothing

}

}

Client类为模拟的客户端类,其中的func函数的功能是根据传入的参数返回一个具体的RealOperation类对象或者NullOperation类对象;主函数中调用func函数传参-1得到空对象,调用其中的request方法:

public class Client {

public static void main(String[] args) {

AbstractOperationabstractOperation = func(-1);

abstractOperation.request();

}

public static AbstractOperationfunc(int para) {

if (para < 0) {

return new NullOperation();

}

return new RealOperation();

}

}

运行结果如图2所示(很好地防止空指针异常的错误):

经过运行,发现此程序的设计方式可以很好地防止空指针异常的错误信息,且把代码的主动权交给了系统,使得代码更加优雅,可维护性更强。

注:若未使用空对象模式,在func函数中设置传参小于0的条件下returnnull,则会出现空指针异常的错误信息,如图3所示。

2 实例与解析

下面以图书管理的场景为例,进一步讲解空对象的设计方式与优势所在。

2.1 应用场景

图书馆查询系统应该可以通过传入图书的ID来获取指定的图书对象,并调用此圖书对象中的方法得到此书信息。

具体的要求是这样的:输入图书的ID,调用一个查询的方法并在方法中传入图书的ID,然后系统会返回给你要查找的图书对象,由此可以调用这个图书对象的输出图书相关信息方法来输出客户需要的图书信息。

2.2 传统解决方式及不足

首先我们来简单讲述一下传统的解决方式。

有一个图书类,其中包括图书ID和作者等其他图书信息字段,包含了构造函数和show函数,用于展示图书相关信息,具体代码如下所示:

public class ConcreteBook {

private int ID;

private String name;

private String author;

public ConcreteBook(int ID, String name, String author) {

this.ID = ID;

this.name = name;

this.author = author;

}

public void show() {

System.out.println(ID + "**" + name + "**" + author);

}

}

需要有图书工厂来创建图书对象,通过传入图书的ID,使用switch语句选择,返回相应ID的图书对象,具体代码如下所示:

public class BookFactory {

public ConcreteBookgetBook(int ID) {

ConcreteBook book = null;

switch (ID) {

case 1:

book = new ConcreteBook(ID, "设计模式", "GoF");

break;

case 2:

book = new ConcreteBook(ID, "空对象模

式", "Null Object Pattern");

break;

default:

book = null;

break;

}

return book;

}

}

在客户端输入图书ID为1,进行运行,具体代码如下所示:

public class Client {

public static void main(String[] args) {

BookFactorybookFactory = new BookFactory();

ConcreteBook book = bookFactory.getBook(1);

book.show();

}

}

可以正常运行,如图4所示:

但是,当我们传入参数为-1时,bookFactory对象返回null,调用null的getBook方法将导致空指针异常的错误,如图5所示。

有一种常见的解决方式,在客户端加一个判断,判断是否为null:如果为null的话,就不再调用show()方法;如果不为null再调用show()方法。这样的做法确实可以避免此问题的出现,改進后的主函数代码如下所示:

public class BookFactory {

public static void main(String[] args) {

BookFactorybookFactory = new BookFactory();

ConcreteBook book = bookFactory.getBook(-1);

if (book == null) {

System.out.println("book对象为 null。");

} else {

book.show();

}

}

这样做确实消除了报错,但是却存在很多弊端:

(1)如果在一段程序中有很多处调用getBook()方法或者有很多个客户端,那么很多处都要判断book对象是否为null。

(2)如果有一处没有判断,发生报错,很有可能导致程序没法继续运行甚至崩溃。

(3)这样做还把整个程序的稳定性寄托在客户端身

上,这样的处理方法,当获取对象为null的时候,输出的提示信息是由客户端来定制的,这样把主动权交给了客户端,而不是我们系统本身。

2.3 使用空对象模式解决

基于传统解决方式的弊端,提出用空对象模式来解决此问题。

引入接口类Book,其中isNull方法判断Book对象是否为空对象,show展示Book对象的信息内容,Book类的代码如下所示:

interface Book {

public booleanisNull();

public void show();

}

空对象类NullBook和具体书类ConcreteBook实现此接口。

空对象类NullBook的代码如下所示:

public class NullBook implements Book {

public booleanisNull() {

return true;

}

public void show() {

System.out.println("Sorry,未找到符合您输入的ID的图书信息,请确认您输入的不是非法值。");

}

}

ConcreteBook类在原有的ConcreteBook类的基础上,增加对Book接口的实现,实现其中的isNull方法,具体代码如下所示:

public class ConcreteBook implements Book{

private int ID;

private String name;

private String author;

public ConcreteBook(int ID, String name, String author) {

this.ID = ID;

this.name = name;

this.author = author;

}

public void show() {

System.out.println(ID + "**" + name + "**" + author);

}

public booleanisNull(){

return false;

}

}

BookFactory类中的方法根据输入的参数创建对应的对象,返回对象从ConcreteBook改为Book,这也体现了我们面向抽象编程的思想,具体代码如下所示:

public class BookFactory {

public Book getBook(int ID) {

Book book;

switch (ID) {

case 1:

book = new ConcreteBook(ID, "设计模式", "GoF");

break;

case 2:

book = new ConcreteBook(ID, "空对象模式", "Null Object Pattern");

break;

default:

book = new NullBook();

break;

}

return book;

}

}

客户端主函数代码也进行对应的修改:

public static void main(String[] args) {

BookFactorybookFactory = new BookFactory();

Book book = bookFactory.getBook(-1);

book.show();

}

执行一下Client,可以发现控制台输出为:Sorry,未找到符合您输入的ID的图书信息,请确认您输入的不是非法值。详细运行结果如图6所示:

我们发现,此时,即使传入的参数是非法值或者不存在的值时,也不会报错。并且,把判断条件从客户端转移到了系统中,实现了一处定制,处处输出。体现了空对象模式在此应用环境下强大的优越性。

我们可以得到使用了空对象模式的类图如图7所示:

当然,虽然在客户端不进行检测也可以保证程序不报错,但是我们也可以在客户端进行相应的检测。使用if判断book.isNull(),而非之前的book == null判断,同样可以对客户端的进行设计,相比之下,book.isNull()比book == null更优雅,思路类似,这里便不过多赘述。

2.4 两种解决方式的比较

对于图书管理场景的传统实现方式和空对象模式两种实现方式的分析,我们可以对两种解决方式进行比较:

(1)傳统的解决方式则容易产生空指针异常的错误信息;空对象模式能有效地防止空指针报错对整个系统的影响,使系统更加稳定,即使传入的参数是非法值或者不存在的值时,也不会报错。

(2)传统的方式主动权在客户端;使用空对象模式能够实现对空对象情况的定制化的控制。

(3)传统的解决方式使用==null判空;而空对象模式通过isNull判空,显得更加优雅,更加易懂。

3 效果与分析

3.1 应用场景

空对象模式是程序或系统中对null检查过多,为了增强代码的鲁棒性和可读性使用。可适用于不想把对null的检查交给客户端,降低系统的安全性时使用。

更为主要的是空对象模式是适用在对null检查后,期望做的事情是一个不做任何动作的关系时,这时能使用空对象模式。

3.2 遵循的设计原则

在软件设计和开发中,为了提高软件系统的可维护性和可复用性,增加软件的可扩展性和灵活性,需要尽量根据七条原则来开发程序,从而提高软件开发效率、节约软件开发成本和维护成本。

空对象模式符合单一职责原则、依赖倒置原则、迪米特法则、开闭原则,因此能较好的提高程序和系统的稳定性。

3.3 空对象模式优点

由上述分析可知,空对象模式具有如下优点:

(1)空对象模式符合软件设计的基本原则,可以加强系统的稳固性,能够有效地防止空指针报错对整个系统的影响,使系统更加稳定。

(2)空对象模式能够实现对空对象情况的定制化的控制,能够掌握处理空对象的主动权,主动权在于系统,而不是客户端。

(3)空对象模式并不依靠Client客户端来保证整个系统的稳定运行。

(4)空对象模式可以降低“错误处理”开销,不至于经常出现空指针异常报错的问题,使程序的稳定性更好。

3.4 空对象模式缺点

虽然空对象模式有很多优点,但是我们也不难发现它具有一定的缺点:

(1)如果各种客户端对null对象应该如何做都不可以达成共识(如未正确定义AbstractObject接口时),则可能难以实现。

(2)可能需要为每个新的AbstractObject类创建一个

新的NullObject类,会增加类以及结构和层次的复杂性。

(3)使用时不知道是空值,如果接口类的需要使用异常等则不能使用。

4 结论

4.1 空对象模式总结

空对象模式是常用的软件设计模式以外的模式,但是对于开发者以及系统本身而言,这种设计模式都是很有利的,空对象模式可以使编程更加方便,使代码更加规范,使系统更加安全。

本文针对在编程的过程中,每次使用引用时,需要大量繁琐的代码测试其是否为空,且把主动权交给客户而非系统本身,在Java编程过程中NullPointerException空指针异常的报错信息的经常出现,会导致系统出现异常甚至停机的情况,由此提出了空对象模式的一个应用,而在实际系统出现空的情况下,系统即使出现错误后也不应该因为客户端请求为空而停机,而是应该返回相应的空对象以避免系统的崩溃,因此在大多数实际场景中,空对象是一个良好的应对策略。

4.2 设计模式总结

总的来说,设计模式主要是分为三大类:创建型模式、结构型模式、行为型模式,其实还有并发型模式和线程池模式。在这些模式中都提供了对应问题的核心解决方案,都是沿用了“不是解决任何问题都需要从头做起”的思想,都是针对接口编程,而不是针对实现编程,都是优先使用组合而不是类继承,都是把设计去支持变化,有着对程序和系统的预见性。

在我们对一个设计模式的应用之前,都应该考虑这个模式能解决什么问题,采用这个模式解决是否是最佳方案,是否有着想得到的效果等。每一种设计模式都是建立在对系统变化点的基础上进行,我们在面对新问题时,其设计模式也应以演化的方式来获得,通过不断的改进才得以准确定位。

最后,设计模式体现的是一种思想,而思想是指导行为的一切,理解和掌握了设计模式,并不代表获得了设计模式的一切,更多接受的是一种思想的熏陶和洗礼,把思想融入实际的设计与开发中去,才是最为重要的。

参考文献:

[1]张扬嵩.Java程序设计中空对象的应用[J].电脑编程技巧与维护,2011(13):87-88.

[2]温立辉.软件设计模式分析[J].科技创新与应用,2020(7):92-93.

[3]郑苗.基于Java的设计模式理解与实现[J].电脑知识与技术,2017,13(032):115-116.

[4]邱士超.被遗忘的设计模式——空对象模式[EB/OL].https://blog.csdn.net/qiumengchen12/article/details/44923139.

[5]杨俊峰.软件设计模式的最佳实践探索[J].中外企业家,2019(27):201.

[6]Peter Vogt, Landscape Ecology. Patterns in software design[J].2019,34(9):2083-2089.

猜你喜欢
设计模式软件工程
“1+1”作业设计模式的实践探索
智慧图书馆环境下的融贯式服务设计模式研究
依托工作室的软件工程实践教学研究
基于工程教育认证的《软件工程》课程教学质量建设研究 
关于如何创新和完善计算机软件工程管理的探讨
基于生产者/消费者设计模式的连续音频信号采集系统
浅析基于问题的教学设计模式