今天,我将讨论较少使用的空对象模式。在面向对象编程中,我们经常处理空对象。空对象是指没有任何引用的对象,或者定义为中性/空功能/行为的对象。在访问任何成员或调用任何方法时,需要检查这些null对象,以确保它们不是null。这是因为成员或方法通常不能在null对象上调用。
让我们看一个例子来更好地理解这个模式。
创建一个抽象类(或接口)来指定各种功能。在这个示例中,我使用了shape接口。请注意,我在接口中也创建了isNull()方法。有一个方法很好,我喜欢它,因为我可以更好地识别和控制空定义的对象。该方法将为所有的具体类返回false。而且,它只会为null对象类返回true。
package design.nullobject;
public interface Shape {
double area();
double perimeter();
void draw();
// nice to have method to indicate null object
boolean isNull();
}
您将需要创建一个具体的类来扩展这个类或实现接口。每个具体类将定义功能的特定版本。我定义了三种形状:圆形、矩形和三角形。这些具体的类将定义不同类型的形状。
下面是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类来创建各种类型的形状。
为这个示例创建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对象。
最后一步,创建一个主类来执行和测试代码:
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。
该类的目的是提供一个类型级别的解决方案,用于表示可选值,而不是使用空引用。
下面,我将演示java.util.Optional的一些有用api。有几个静态api来创建可选对象:
Optional.empty()
: 要创建一个空的可选对象,使用空API:
@Test
public void optionalEmptyTest() {
Optional<Shape> empty = Optional.empty();
assertFalse(empty.isPresent());
}
Optional.of()
: 要创建一个非空的可选对象,如果我们确信我们有一个我们想让它成为可选的对象,使用API。如果您使用null调用这个API,它将抛出null指针异常 ( NullPointerException
)。
@Test
public void optionalOfTest() {
Shape shape = ShapeFactory.createShape("Circle");
Optional<Shape> nonEmpty = Optional.of(shape);
assertTrue(nonEmpty.isPresent());
}
@Test(expected = NullPointerException.class)
public void optionalOfWithNullTest() {
Shape shape = null;
Optional.of(shape);
}
Optional.ofNullable()
:在上面的代码中,当我们处于空或非空状态时,这两个api都很有用。如果我们不确定对象,对象可能为空,也可能不为空。为此,使用 ofNullable
API。
@Test
public void optionalOfNullableTest() {
Shape shape1 = ShapeFactory.createShape("Circle");
Optional<Shape> nonEmpty = Optional.ofNullable(shape1);
assertTrue(nonEmpty.isPresent());
Shape shape2 = null;
Optional<Shape> empty = Optional.ofNullable(shape2);
assertFalse(empty.isPresent());
}
isPresent()
:此方法返回true,且仅当包装在可选对象中的对象不是空的(非空)。
@Test
public void optionalIsPresentTest() {
Shape shape = ShapeFactory.createShape("Circle");
Optional<Shape> nonEmpty = Optional.of(shape);
assertTrue(nonEmpty.isPresent());
Optional<Shape> empty = Optional.empty();
assertFalse(empty.isPresent());
}
ifPresent()
: 这使我们能够在包装值上运行代码,如果发现它是非空的。
@Test
public void optionalIfPresentTest() {
Shape shape = ShapeFactory.createShape("Circle");
Optional<Shape> nonEmpty = Optional.of(shape);
nonEmpty.ifPresent(circle -> circle.draw());
Optional<Shape> empty = Optional.empty();
empty.ifPresent(circle -> circle.draw());
}
get()
:如果包装对象不为空,则返回一个值。否则,它将抛出一个没有此类元素异常(NoSuchElementException)。
@Test
public void optionalGetTest() {
Shape shape = ShapeFactory.createShape("Circle");
Optional<Shape> nonEmpty = Optional.ofNullable(shape);
assertNotNull(nonEmpty.get());
}
@Test(expected = NoSuchElementException.class)
public void optionalGetWithNullTest() {
Shape shape = null;
Optional<Shape> empty = Optional.ofNullable(shape);
empty.get();
}
orElse()
: 这用于检索包装在可选实例中的对象。该方法有一个参数作为默认对象。使用orElse,如果包装对象存在,则返回包装对象,如果包装对象不存在,则返回给orElse的参数。
@Test
public void optionalOrElseTest() {
Shape shape = ShapeFactory.createShape("Rectangle");
Shape shape1 = ShapeFactory.createShape("Circle");
Optional<Shape> nonEmpty = Optional.of(shape1);
assertEquals(shape1, nonEmpty.orElse(shape));
Optional<Shape> empty = Optional.empty();
empty.ifPresent(circle -> circle.draw());
assertEquals(shape, empty.orElse(shape));
}
orElseGet()
: 这个方法与orElse类似。唯一的区别是,如果可选对象不存在,它不接受返回的对象,而是接受一个supplier functional接口,该接口被调用并返回调用的值。
@Test
public void optionalOrElseGetTest() {
Shape shape = ShapeFactory.createShape("Rectangle");
Shape shape1 = ShapeFactory.createShape("Circle");
Optional<Shape> nonEmpty = Optional.of(shape1);
assertEquals(shape1, nonEmpty.orElseGet(() -> ShapeFactory.createShape("Rectangle")));
Optional<Shape> empty = Optional.empty();
empty.ifPresent(circle -> circle.draw());
// comparing the area of the shape since orElseGet will create another instance of rectangle
assertEquals(shape.area(), empty.orElseGet(() -> ShapeFactory.createShape("Rectangle")).area(), 0.001d);
}
orElseThrow()
: 这与orElse类似。唯一的区别是,它添加了一种处理缺失值的新方法。当包装对象不存在时,它不会返回默认值,而是抛出一个给定的(作为参数)异常。
@Test(expected = IllegalArgumentException.class)
public void optionalOrElseThrowWithNullTest() {
Shape shape = null;
Optional<Shape> empty = Optional.ofNullable(shape);
empty.orElseThrow(IllegalArgumentException::new);
}
现在,我们可以使用Optional来编写ShapeFactory和Main,如下所示:
package design.nullobject;
public class ShapeFactoryJava8 {
public static Optional<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 {
// no need to have NullShape anymore
shape = null;
}
// using ofNullable because shape may be not null.
return Optional.ofNullable(shape);
}
}
执行和测试代码的主类:
package design.nullobject;
import java.util.Arrays;
import java.util.Optional;
public class ShapeMainJava8 {
public static void main(String[] args) {
String[] shapeTypes = new String[] { "Circle", null, "Triangle", "Pentagon", "Rectangle", "Trapezoid"};
Arrays.asList(shapeTypes).stream().forEach(shapeType -> {
Optional<Shape> optionalShape = ShapeFactoryJava8.createShape(shapeType);
optionalShape.ifPresent((shape) -> {
// null-check is done by ifPresent of Optional
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.4330127018922193
Shape Perimeter: 3.0
Drawing Triangle with area: 0.4330127018922193 and perimeter: 3.0
Shape area: 1.0
Shape Perimeter: 4.0
Drawing Rectangle with area: 1.0 and perimeter: 4.0