今天,我正在讨论一种较少使用的模式,称为空对象模式。在面向对象的编程中,我们经常处理空对象。空对象是指没有任何引用的对象或使用中性/空功能/行为定义的对象。在访问任何成员或调用任何方法时,需要检查这些空对象以确保它们不为空。这是因为成员或方法通常无法在空对象上调用。
空对象模式
空对象设计模式描述了空对象的用法及其在系统中的行为。
·
空对象模式处理空对象。
·
·
而不是检查null对象,我们定义了null行为或调用了do-nothing行为。
·
·
这些空对象还可以用于在数据不可用的情况下提供默认行为。
·
·
这种方法优于有效的默认实现的优势在于,空对象非常容易预测,并且没有副作用-它 什么也不做。
·
·
如果资源不可用于测试,则空对象模式还可以用作测试的存根。
·
在使用空对象模式之前,我们应该了解:
·
该模式应谨慎使用。它可以使错误显示为正常程序执行。
·
·
我们不应该仅仅为了避免空检查而使代码更具可读性就实现这种模式。实际上,很难读取移至另一个位置的代码,例如null对象类。
·
·
我们必须执行其他测试以确保没有地方要分配null而不是null对象。
·
让我们看一个例子,以更好地理解这种模式。
空对象的示例
·
创建一个抽象类(或接口)以指定各种功能。我在本示例中使用shape接口。请注意,我也在isNull() 界面中创建了一个方法 。有一个方法很好,而且我喜欢它,因为我可以更好地识别和控制空定义的对象。false 对于所有的具体类,此方法将返回 。并且,它将true 仅针对空对象类返回 。
·
package design.nullobject;
public interface Shape {
double area();
double perimeter();
void draw();
// nice to have method to indicate null object
boolean isNull();
}
您将需要创建一个扩展该类或实现该接口的具体类。每个具体的类都将定义功能的特定版本。我定义三种形状: Circle , Rectangle ,和 Triangle。这些具体的类将定义不同类型的形状。下面是Circle 该类的代码 :
package design.nullobject;
public class Circle implements Shape {
// sides
private final double radius;
public Circle() {
this(1.0d);
}
public Circle(double radius) {
this.radius = radius;
}
@Override public double area() {
// Area = π r^2
return Math.PI * Math.pow(radius, 2);
}
@Override public double perimeter() {
// Perimeter = 2πr
return 2 * Math.PI * radius;
}
@Override public void draw() {
System.out.println("Drawing Circle with area: " + area() + " and perimeter: " + perimeter());
}
@Override
public boolean isNull() {
return false;
}
}
下面是Rectangle 该类的代码 :
package design.nullobject;
public class Rectangle implements Shape {
// sides
private final double width;
private final double length;
public Rectangle() {
this(1.0d ,1.0d);
}
public Rectangle(double width, double length) {
this.width = width;
this.length = length;
}
@Override
public double area() {
// A = w * l
return width * length;
}
@Override
public double perimeter() {
// P = 2(w + l)
return 2 * (width + length);
}
@Override
public void draw() {
System.out.println("Drawing Rectangle with area: " + area() + " and perimeter: " + perimeter());
}
@Override
public boolean isNull() {
return false;
}
}
下面是Triangle 该类的代码 :
package design.nullobject;
public class Triangle implements Shape {
// sides
private final double a;
private final double b;
private final double c;
public Triangle() {
this(1.0d, 1.0d, 1.0d);
}
public Triangle(double a, double b, double c) {
this.a = a;
this.b = b;
this.c = c;
}
@Override
public double area() {
// Using Heron's formula:
// Area = SquareRoot(s * (s - a) * (s - b) * (s - c))
// where s = (a + b + c) / 2, or 1/2 of the perimeter of the triangle
double s = (a + b + c) / 2;
return Math.sqrt(s * (s - a) * (s - b) * (s - c));
}
@Override
public double perimeter() {
// P = a + b + c
return a + b + c;
}
@Override public void draw() {
System.out.println("Drawing Triangle with area: " + area() + " and perimeter: " + perimeter());
}
@Override
public boolean isNull() {
return false;
}
}
现在,最重要的步骤是创建一个空对象类,该对象类扩展了抽象类或接口并定义了“不执行操作”行为。万一数据不可用,“不执行任何操作”行为就像默认行为。
package design.nullobject;
public class NullShape implements Shape {
// no sides
@Override
public double area() {
return 0.0d;
}
@Override
public double perimeter() {
return 0.0d;
}
@Override
public void draw() {
System.out.println("Null object can't be draw");
}
@Override
public boolean isNull() {
return true;
}
}
现在,我们定义 Factory 该类以创建各种类型的形状。请参阅(Java中的策略vs工厂设计模式)以了解工厂模式。我ShapeFactory 为此示例创建 类。
package design.nullobject;
public class ShapeFactory {
public static Shape createShape(String shapeType) {
Shape shape = null;
if ("Circle".equalsIgnoreCase(shapeType)) {
shape = new Circle();
} else if ("Rectangle".equalsIgnoreCase(shapeType)) {
shape = new Rectangle();
} else if ("Triangle".equalsIgnoreCase(shapeType)) {
shape = new Triangle();
} else {
shape = new NullShape();
}
return shape;
}
}
为了使示例简单,我没有在ShapeFactory 方法中收到形状边的参数 。因此,工厂正在创建Shape 具有固定边值的其他 对象。
·
并且,在最后一步,创建一个 Main 类来执行和测试代码:
·
package design.nullobject;
import design.nullobject.ShapeFactory;
public class ShapeMain {
public static void main(String[] args) {
String[] shapeTypes = new String[] { "Circle", null, "Triangle", "Pentagon", "Rectangle", "Trapezoid"};
for (String shapeType : shapeTypes) {
Shape shape = ShapeFactory.createShape(shapeType);
// no null-check required since shape factory always creates shape objects
System.out.println("Shape area: " + shape.area());
System.out.println("Shape Perimeter: " + shape.perimeter());
shape.draw();
System.out.println();
}
}
}
下面是代码的输出:
Shape area: 3.141592653589793
Shape Perimeter: 6.283185307179586
Drawing Circle with area: 3.141592653589793 and perimeter: 6.283185307179586
Shape area: 0.0
Shape Perimeter: 0.0
Null object can't be draw
Shape area: 0.4330127018922193
Shape Perimeter: 3.0
Drawing Triangle with area: 0.4330127018922193 and perimeter: 3.0
Shape area: 0.0
Shape Perimeter: 0.0
Null object can't be draw
Shape area: 1.0
Shape Perimeter: 4.0
Drawing Rectangle with area: 1.0 and perimeter: 4.0
Shape area: 0.0
Shape Perimeter: 0.0
Null object can't be draw
在Java 8中,我们具有 java.util.Optional 处理空引用的类。此类最初来自Guava API。