空对象模式

目的

空对象模式并不是 GoF 23 种经典设计模式之一,但它是一种(在软件开发中)经常用到的设计模式,以至于可单独作为一种设计模式存在。

空对象模式有以下优点:

  • 简化客户端代码
  • 减少抛出空指针异常的几率
  • 需要更少的测试用例

在该模式中,一个返回对象或 null 的方法,应该相应地返回那个业务对象或 NullObjectNullObject 通过消除客户端代码中的条件检查达到简化代码的目的,例如:

1
2
3
4
5
6
7
// 未使用 NullObject
if (!is_null($obj)) {
$obj->callSomething();
}

// 使用 NullObject,可简化为
$obj->callSomething();

使用场景

  • 空日志记录器或空输出对象,在需要保留对象之间的标准交互方式时(即使什么也不做)
  • 职责链模式中使用 null 处理程序时
  • 命令模式中使用 null 命令时

UML 类图

空对象模式类图

代码

Service.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php declare(strict_types = 1);

namespace DesignPatterns\Extend\NullObject;

class Service
{
private Logger $logger;

public function __construct(Logger $logger)
{
$this->logger = $logger;
}

/**
* do something ...
*/
public function doSomething()
{
// notice here that you don't have to check if the logger is set with eg. is_null(), instead just use it
$this->logger->log('We are in '.__METHOD__);
}
}

Logger.php

1
2
3
4
5
6
7
8
9
10
11
<?php declare(strict_types = 1);

namespace DesignPatterns\Extend\NullObject;

/**
* Key feature: NullLogger must inherit from this interface like any other loggers
*/
interface Logger
{
public function log(string $str);
}

PrintLogger.php

1
2
3
4
5
6
7
8
9
10
11
<?php declare(strict_types = 1);

namespace DesignPatterns\Extend\NullObject;

class PrintLogger implements Logger
{
public function log(string $str)
{
echo $str;
}
}

NullLogger.php

1
2
3
4
5
6
7
8
9
10
11
<?php declare(strict_types = 1);

namespace DesignPatterns\Extend\NullObject;

class NullLogger implements Logger
{
public function log(string $str)
{
// do nothing
}
}

测试

Tests/LoggerTest.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php declare(strict_types = 1);

namespace DesignPatterns\Extend\NullObject\Tests;

use DesignPatterns\Extend\NullObject\NullLogger;
use DesignPatterns\Extend\NullObject\PrintLogger;
use DesignPatterns\Extend\NullObject\Service;
use PHPUnit\Framework\TestCase;

class LoggerTest extends TestCase
{
public function testNullObject()
{
$service = new Service(new NullLogger());
$this->expectOutputString('');
$service->doSomething();
}

public function testStandardLogger()
{
$service = new Service(new PrintLogger());
$this->expectOutputString('We are in DesignPatterns\Extend\NullObject\Service::doSomething');
$service->doSomething();
}
}