今天,我將討論較少使用的空對象模式。在面向對象編程中,我們經常處理空對象。空對象是指沒有任何引用的對象,或者定義爲中性/空功能/行爲的對象。在訪問任何成員或調用任何方法時,需要檢查這些null對象,以確保它們不是null。這是因爲成員或方法通常不能在null對象上調用。
Null Object Pattern
- null對象設計模式描述了null對象的使用及其在系統中的行爲。
- 空對象模式處理空對象。
- 我們不檢查空對象,而是定義空行爲或調用不做行爲。
- 這些null對象還可以用於在數據不可用的情況下提供默認行爲。
- 這種方法相對於可工作的默認實現的優點是null對象是非常可預測的,並且沒有副作用——它什麼也不做。
- null對象模式還可以用作測試的存根,以防測試無法使用資源。
在使用Null對象模式之前,我們應該瞭解:
- 這種模式應謹慎使用。它可以使錯誤出現在正常的程序執行中。
- 我們不應該僅僅爲了避免null檢查和使代碼更易於閱讀而實現這種模式。實際上,如果代碼移動到另一個地方,比如null對象類,那麼讀取它就會比較困難。
- 我們必須執行額外的測試,以確保沒有任何地方需要指定null而不是null對象。
讓我們看一個例子來更好地理解這個模式。
Example of Null Objects
創建一個抽象類(或接口)來指定各種功能。在這個示例中,我使用了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。
Optional
該類的目的是提供一個類型級別的解決方案,用於表示可選值,而不是使用空引用。
下面,我將演示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()); }
Additional APIs
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