前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java中Null Object 设计模式

Java中Null Object 设计模式

作者头像
程序你好
发布2018-09-29 11:03:04
9480
发布2018-09-29 11:03:04
举报
文章被收录于专栏:程序你好程序你好

今天,我将讨论较少使用的空对象模式。在面向对象编程中,我们经常处理空对象。空对象是指没有任何引用的对象,或者定义为中性/空功能/行为的对象。在访问任何成员或调用任何方法时,需要检查这些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。

代码语言:javascript
复制
        package design.nullobject;        
        
        public interface Shape {
        
        double area();
            double perimeter();
            void draw();
            // nice to have method to indicate null object
            boolean isNull();
        }

您将需要创建一个具体的类来扩展这个类或实现接口。每个具体类将定义功能的特定版本。我定义了三种形状:圆形、矩形和三角形。这些具体的类将定义不同类型的形状。

下面是Circle类的代码:

代码语言:javascript
复制
        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 类的代码:

代码语言:javascript
复制
        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 类:

代码语言:javascript
复制
        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;
            }
        }

现在,最重要的一步是创建一个空对象类,它扩展抽象类或接口并定义“不做任何事情”行为。“不做”行为类似于在数据不可用的情况下的默认行为。

代码语言:javascript
复制
        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类。

代码语言:javascript
复制
        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对象。

最后一步,创建一个主类来执行和测试代码:

代码语言:javascript
复制
        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();
                }
            }
        
        }

下面是代码的输出:

代码语言:javascript
复制
        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:

代码语言:javascript
复制
        @Test        
        public void optionalEmptyTest() {
            Optional<Shape> empty = Optional.empty();
            assertFalse(empty.isPresent());
        }

Optional.of(): 要创建一个非空的可选对象,如果我们确信我们有一个我们想让它成为可选的对象,使用API。如果您使用null调用这个API,它将抛出null指针异常 ( NullPointerException )。

代码语言:javascript
复制
        @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。

代码语言:javascript
复制
        @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,且仅当包装在可选对象中的对象不是空的(非空)。

代码语言:javascript
复制
        @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(): 这使我们能够在包装值上运行代码,如果发现它是非空的。

代码语言:javascript
复制
        @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)。

代码语言:javascript
复制
        @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的参数。

代码语言:javascript
复制
        @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接口,该接口被调用并返回调用的值。

代码语言:javascript
复制
        @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类似。唯一的区别是,它添加了一种处理缺失值的新方法。当包装对象不存在时,它不会返回默认值,而是抛出一个给定的(作为参数)异常。

代码语言:javascript
复制
        @Test(expected = IllegalArgumentException.class)        
        public void optionalOrElseThrowWithNullTest() {
          Shape shape = null;
          Optional<Shape> empty = Optional.ofNullable(shape);
          empty.orElseThrow(IllegalArgumentException::new);
        }

现在,我们可以使用Optional来编写ShapeFactory和Main,如下所示:

代码语言:javascript
复制
        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);
        }
        }

执行和测试代码的主类:

代码语言:javascript
复制
        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();
                    });
                });
            }
        
        }

下面是代码的输出:

代码语言:javascript
复制
        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
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-08-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序你好 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Null Object Pattern
  • 在使用Null对象模式之前,我们应该了解:
  • Example of Null Objects
  • Optional
    • Additional APIs
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档