空对象模式(Null Object)

目的

空对象模式不属于 GoF 设计模式,但是它作为一种经常出现的套路足以被视为设计模式了。它具有如下优点:

  • 客户端代码简单
  • 可以减少报空指针异常的几率
  • 测试用例不需要考虑太多条件

返回一个对象或 null 应该用返回对象或者 NullObject 代替。NullObject 简化了死板的代码,消除了客户端代码中的条件检查,例如 if (!is_null($obj)) { $obj->callSomething(); } 只需 $obj->callSomething(); 就行。

例子

  • Symfony2: 空日志
  • Symfony2: Symfony/Console 空输出
  • 责任链模式中的空处理器
  • 命令行模式中的空命令

UML 图

file

代码

你也可以在 GitHub 上查看此代码

Service.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\NullObject;
  3. /**
  4. * 创建服务类 Service 。
  5. */
  6. class Service
  7. {
  8. /**
  9. * @var LoggerInterface
  10. * 定义日记类对象。
  11. */
  12. private $logger;
  13. /**
  14. * @param LoggerInterface $logger
  15. * 传入日记类对象参数。
  16. */
  17. public function __construct(LoggerInterface $logger)
  18. {
  19. $this->logger = $logger;
  20. }
  21. /**
  22. * 做些什么。。。
  23. * 在日记中返回了 '我们在 Service: doSomething 里' 。
  24. */
  25. public function doSomething()
  26. {
  27. // 提示:这里你只是使用它,而不需要通过如:is_null() 检查 $logger 是否已经设置。
  28. $this->logger->log('We are in '.__METHOD__);
  29. }
  30. }

LoggerInterface.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\NullObject;
  3. /**
  4. * 重要特征:空日记必须像其他日记意向从这个接口继承。
  5. */
  6. interface LoggerInterface
  7. {
  8. public function log(string $str);
  9. }

PrintLogger.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\NullObject;
  3. /**
  4. * 创建一个日记打印类实现日记接口。
  5. */
  6. class PrintLogger implements LoggerInterface
  7. {
  8. public function log(string $str)
  9. {
  10. echo $str;
  11. }
  12. }

NullLogger.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\NullObject;
  3. /**
  4. * 创建一个空日记类实现日记接口。
  5. */
  6. class NullLogger implements LoggerInterface
  7. {
  8. public function log(string $str)
  9. {
  10. // 什么也不用做
  11. }
  12. }

测试

Tests/LoggerTest.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\NullObject\Tests;
  3. use DesignPatterns\Behavioral\NullObject\NullLogger;
  4. use DesignPatterns\Behavioral\NullObject\PrintLogger;
  5. use DesignPatterns\Behavioral\NullObject\Service;
  6. use PHPUnit\Framework\TestCase;
  7. /**
  8. * 创建测试单元 LoggerTest 。
  9. */
  10. class LoggerTest extends TestCase
  11. {
  12. /**
  13. * 测试 NullLogger 对象,联系上文可以知道什么也没做。
  14. */
  15. public function testNullObject()
  16. {
  17. $service = new Service(new NullLogger());
  18. $this->expectOutputString('');
  19. $service->doSomething();
  20. }
  21. /**
  22. * 测试 PrintLogger 对象,联系上文可以知道在日记中写入了 DesignPatterns\Behavioral\NullObject\Service::doSomething 。
  23. */
  24. public function testStandardLogger()
  25. {
  26. $service = new Service(new PrintLogger());
  27. $this->expectOutputString('We are in DesignPatterns\Behavioral\NullObject\Service::doSomething');
  28. $service->doSomething();
  29. }
  30. }