设计模式之空对象模式 浏览次数:576 日期:2020-07-17 12:50:00 ### 情景分析 在项目开发过程中,经常会使用各种各样的日志记录系统,根据业务场景有的日志写到文件,有的存储到Database,还有的存储到es或者mongodb里面,这时候经常定义一个接口规范,然后各种日志驱动类去实现这个接口,代码如下: ```php info('基本信息内容'); echo ""; $logger ->error('错误信息内容'); ``` 运行结果显然是输出了 ```shell 这是file的info方法:基本信息内容 这是file的error方法:错误信息内容 ``` 然后我们把使用方式的$logger = getLogger('file')改成getLogger('db')也可以正常输出,那我们改成getLogger('database')或者getLogger('mongodb')会怎么样呢? ```php $logger = getLogger('database'); $logger ->info('基本信息内容'); echo ""; $logger ->error('错误信息内容'); ``` 结果是...报错了 ```text Fatal error: Call to a member function info() on null in E:\project\chain\logistics\d.php on line 50 ``` 写过几年代码的同行应该都遇到过这种错误,错误原因很简单: 我们通过getLogger函数获取Logger对象的时候,如果我们传入的参数字符串非file和db时,就会返回null,表示我们暂时没有logger驱动支持。这时logger变量为null,再调用info、error就会报fatal error错误了。 我们比较常规的做法就是在使用logger时加一个判断,判断是否为null,如果为null的话,就不再调用info、error方法。如果不为null再调用方法。更改如下: ```php $logger = getLogger('database'); if($logger) { $logger ->info('基本信息内容'); echo ""; $logger ->error('错误信息内容'); } ``` 这时候再运行,就不会报错了。 --- 这样做,确实消除了报错,但是这样做真的好吗?你想如果在一段程序中有很多处调用getLogger写日志,岂不是很多处都要判断logger对象是否为null?稍有不注意可能程序代码就报错了。最重要的是,永远都不要太相信方法使用者或者说客户端,不要把整个程序的稳定性寄托在使用者身上。还有,像上面的处理方法,当获取对象为null的时候,输出的提示信息是有调用者来定制的,这样主动权交给了使用者,而不是logger所有者。 有的人通常会给一个默认实现方式,比如不存在mongodb的实现方式时,我提供file的方式,如下: ```php function getLogger($name) { $logger = null; switch($name) { case 'file': $logger = new FileLogger; break; case 'db': $logger = new DatabaseLogger; break; default: $logger = new FileLogger; break; } return $logger; } ``` 这样的话,工厂方法永远会返回一个实现Logger接口的示例,调用者也不需要添加不必要的if判断了,似乎没什么问题。但是你觉得这样合理吗,就像我给你要一个苹果,然后你没苹果,你给我一个梨,反正都是水果。 那究竟应该如何实现才会更加合适呢?那就要用到我们今天要介绍的空对象模式 ### 空对象模式 定义一个实现Logger接口的空类 ```php class NullLogger implements Logger { public function info($message) { } public function error($message) { } } ``` 修改后的工厂方法如下: ```php function getLogger($name) { $logger = null; switch($name) { case 'file': $logger = new FileLogger; break; case 'db': $logger = new DatabaseLogger; break; default: $logger = new NullLogger; break; } return $logger; } ``` 调用方式如下: ```php $logger = getLogger('database'); $logger ->info('基本信息内容'); echo ""; $logger ->error('错误信息内容'); ``` 运行一下,我们发现,即使传入工厂方法的参数是非法值或者不存在的值时,也不会报错了,这是空对象模式的第一个好处。但是现在不报错,也没有任何输出,肯定不够友好,不够人性化。此时,在NullLogger类的info、error方法中,我们可以定制我们的输出提醒,当用户调用空对象的方法时,就会输出我们定制的提醒。这样我们可以实现一处定制,处处输出,主动权在我们手里,而不是在调用者手里。这是空对象模式的第二个好处,定制代码如下: ```php class NullLogger implements Logger { public function info($message) { throw new Exception("我还没有实现这个方法,我自己定制了输出"); } public function error($message) { } } ``` 空对象最重要的特点是,不需要添加if逻辑判断是否为null了,减少了调用者的代码量,当然有时候调用方根据自己的业务,确实需要知道工厂方法里面是否实现了具体logger驱动,来定制自己的log输出,那可以提供一个isNull方法给调用方,完整代码如下: ```php isNull()) { echo "空对象"; } $logger ->info('基本信息内容'); echo ""; $logger ->error('错误信息内容'); ``` $logger->isNull()比$logger == null更加优雅一点,有面向对象的感觉了 ### 空对象模式作用 - 减少不必要的if为null判断,从而加强系统的稳固性 - 避免Fatal error,防止空指针报错对整个系统的影响 - 实现对空对象情况的定制化控制,能够掌握处理空对象的主动权 最后更新时间为: 8个月前 (2020-07-17 12:50:00)