3.6. Null Object¶
3.6.1. Purpose¶
NullObject is not a GoF design pattern but a schema which appears frequently enough to be considered a pattern. It has the following benefits:
- Client code is simplified
- Reduces the chance of null pointer exceptions
- Fewer conditionals require less test cases
Methods that return an object or null should instead return an object or
NullObject
. NullObject
s simplify boilerplate code such as
if (!is_null($obj)) { $obj->callSomething(); }
to just
$obj->callSomething();
by eliminating the conditional check in
client code.
3.6.2. Examples¶
- Symfony2: null logger of profiler
- Symfony2: null output in Symfony/Console
- null handler in a Chain of Responsibilities pattern
- null command in a Command pattern
3.6.3. UML Diagram¶
3.6.4. Code¶
You can also find these code on GitHub
Service.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 26 27 28 29 30 31 32 33 34 | <?php
namespace DesignPatterns\Behavioral\NullObject;
/**
* Service is dummy service that uses a logger
*/
class Service
{
/**
* @var LoggerInterface
*/
protected $logger;
/**
* we inject the logger in ctor and it is mandatory
*
* @param LoggerInterface $log
*/
public function __construct(LoggerInterface $log)
{
$this->logger = $log;
}
/**
* do something ...
*/
public function doSomething()
{
// no more check "if (!is_null($this->logger))..." with the NullObject pattern
$this->logger->log('We are in ' . __METHOD__);
// something to do...
}
}
|
LoggerInterface.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <?php
namespace DesignPatterns\Behavioral\NullObject;
/**
* LoggerInterface is a contract for logging something
*
* Key feature: NullLogger MUST inherit from this interface like any other Loggers
*/
interface LoggerInterface
{
/**
* @param string $str
*
* @return mixed
*/
public function log($str);
}
|
PrintLogger.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?php
namespace DesignPatterns\Behavioral\NullObject;
/**
* PrintLogger is a logger that prints the log entry to standard output
*/
class PrintLogger implements LoggerInterface
{
/**
* @param string $str
*/
public function log($str)
{
echo $str;
}
}
|
NullLogger.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?php
namespace DesignPatterns\Behavioral\NullObject;
/**
* Performance concerns : ok there is a call for nothing but we spare an "if is_null"
* I didn't run a benchmark but I think it's equivalent.
*
* Key feature : of course this logger MUST implement the same interface (or abstract)
* like the other loggers.
*/
class NullLogger implements LoggerInterface
{
/**
* {@inheritdoc}
*/
public function log($str)
{
// do nothing
}
}
|
3.6.5. Test¶
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 26 27 28 29 30 | <?php
namespace DesignPatterns\Behavioral\NullObject\Tests;
use DesignPatterns\Behavioral\NullObject\NullLogger;
use DesignPatterns\Behavioral\NullObject\Service;
use DesignPatterns\Behavioral\NullObject\PrintLogger;
/**
* LoggerTest tests for different loggers
*/
class LoggerTest extends \PHPUnit_Framework_TestCase
{
public function testNullObject()
{
// one can use a singleton for NullObjet : I don't think it's a good idea
// because the purpose behind null object is to "avoid special case".
$service = new Service(new NullLogger());
$this->expectOutputString(null); // no output
$service->doSomething();
}
public function testStandardLogger()
{
$service = new Service(new PrintLogger());
$this->expectOutputString('We are in DesignPatterns\Behavioral\NullObject\Service::doSomething');
$service->doSomething();
}
}
|