问题
- 经过良好设计的系统一般通过方法调用来传递对象实例。每个类都会与背景环境保持独立,并通过清晰的通讯方式来与系统中其他部分进行协作。有时你需要使用一些作为对象间沟通渠道的类,此时就不得不引入依赖关系。
- 假设有一个用于保存应用程序信息的
Preferences
类。我们可能会使用一个 Perferences 对象来保存诸如DSN(用于保存数据库的表及用户信息)字符串,URL根目录、文件路径等数据。这些信息在你每次部署程序时都可能会有所不同。改对象也可被用作一个“公告板”,它是可以被系统中其他无关对象设置和获取消息的中心。 - 但在对象中传递 Preferences 对象并不总是个好主意。你可以让原来并不使用 Preferences 对象的类强制性地接受 Preferences 对象,以便这些类能传递 Preferences 对象给其它对象,但这样做产生了另一种形式的耦合。
- 我们需要保证系统中的所有对象都使用同一个 Preferences 对象。我们不希望一些对象在一个 Preferences 对象上设值,而其它对象从另外一个完全不同的 Preferences 对象上读取数据。
- 让我们提炼出这个问题的几个关键点:
- Preferences 对象应该可以被系统中的任何对象使用
- Preferences 对象不应该被存储在会被覆写的全局变量中。
- 系统中不应超过一个 Preferences 对象。也就是说,Y对象可以设置 Preferences 对象的一个属性,而Z对象不需要通过其它对象(假设Y和Z都可以访问 Preferences 对象)就可以直接获取该属性的值。
实现
- 使用静态方法和静态属性来间接实例化对象。
class Preferences {
private $props = array();
private static $instance = null;
/**
* 构造函数
*/
private function __construct()
{
}
/**
* 不允许深度复制
*/
private function __clone()
{
}
/**
* 不允许serialize
*/
private function __sleep()
{
}
/**
* 不允许unserialize
*/
private function __wakeup()
{
}
/**
* 获取单例
* @return 实例化后的对象
*/
public static function getInstance()
{
if (empty(self::$instance)) {
self::$instance = new Preferences();
}
return self::$instance;
}
/**
* 设置属性值
* @param string $key 键
* @param string $val 值
* @return boolen 布尔值
*/
public function setProperty($key, $val)
{
$this->props[$key] = $val;
return true;
}
/**
* 获取属性值
* @param string $key 键
* @return $this->props 属性值
*/
public function getProperty($key = null)
{
if (is_null($key)) {
return $this->props;
} else {
return $this->props[$key];
}
}
}
- $instance 属性设置为
private
及static
,因此不能再类外部被访问。而 getInstance() 方法是 public 且 static 的,所有在脚本的任何地方都可被调用。
$pref = Preferences::getInstance();
$pref->setProperty('subject', 'php');
unset($pref); // 移除引用
$pref2 = Preferences::getInstance();
$resp = $pref2->getProperty('subject');
var_dump($resp); //该属性值并没有丢失
response:php
静态方法不能访问普通的对象属性,因为根据静态的定义,它只能被类而不是对象调用。但静态方法可以访问静态属性。所以当 getInstance() 被调用时,我们会检查
Preference::$instance
属性。如果为空,那么创建一个 Preferences 对象实例并把它保存在$instance
属性中,然后我们把实例返回给调用代码。因为静态方法 getInstance() 是 Preferences 类的一部分,所以尽管构造方法是私有的,但是实例化 Preferences 对象完全没有问题。单例模式UML类图