PHP 设计模式 - 单例模式原创

May 28, 2020
haiqing
130
单例对象的类必须保证只有一个实例存在,使用静态对象,减少频繁创建,提高性能。

单例模式,也叫单子模式,是一种常用的软件设计模式。 在应用这个模式时,单例对象的类必须保证只有一个实例存在。 许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。

通俗的说,也就是对于某一个功能只能实例化一个对象。

要点

  1. 一个类只能有一个实例
  2. 它必须自行创建这个实例
  3. 它必须自行向整个系统提供这个实例

具体实现的重点

  1. 单例模式的类只提供私有的构造函数,
  2. 类定义中含有一个该类的静态私有对象,
  3. 该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。

示例代码

 /**
* 单例类
* Singleton.class
*/
class Singleton
{
/**
* 静态成品变量 保存全局实例
*/
private static $_instance = NULL;

/**
* 私有化默认构造方法,保证外界无法直接实例化
*/private function __construct()
{

}

/**
* 静态工厂方法,返还此类的唯一实例
*/public static function getInstance() {
if (is_null(self::$_instance)) {
self::$_instance = new Singleton();

// 或者这样写// self::$_instance = new self();
}

return self::$_instance;
}

/**
* 防止用户克隆实例
*/public function __clone(){
die('Clone is not allowed.' . E_USER_ERROR);
}

/**
* 测试用方法
*/public function test(){
echo 'Singleton Test OK!';
}

}

/**
* 调用
*/
$instance = Singleton::getInstance();
$instance->test();

OOP知识补习

类型运算符instanceof

class MyClass{
}

class NotMyClass{
}
$a = new MyClass;

var_dump($a instanceof MyClass);
var_dump($a instanceof NotMyClass);

以上例程会输出:

bool(true)
bool(false)

instanceof用于确定一个变量是不是实现了某个类,继承类,接口的对象的实例。 如果被检测的变量不是对象,instanceof 并不发出任何错误信息而是返回 FALSE。不允许用来检测常量。

魔术方法__construct()

构造方法声明为private,防止直接创建对象 ,这样new Singleton() 会报错。

private function __construct() { 
echo 'Iam constructed';
}

魔术方法__clone()

当类的复制完成时,如果定义了__clone()方法,则新创建的对象(复制生成的对象)中的__clone() 方法会被调用,可用于修改属性的值(如果有必要的话)。私有化__clone可以防止克隆该类的对象。 注意一点:clone的对象不执行__construct里的方法

所以我们在防止单例模式的 $singleton对象被clone,有两种方法可以做到。

第一种方法:设置魔术方法__clone();访问权限为private 第二种方法:若__clone()为公用方法,则在函数中加上自定义错误。

// 阻止用户复制对象实例
public function __clone(){
trigger_error('Clone is not allowed.',E_USER_ERROR);
}
关于 __clone() , PHP官方的文档: Once the cloing is complete, if a __clone() method is defined, then the newly created object’s __clone() method will be called, to allow any necessary properties that need to be changed.

关键字clone和赋值

class foo {
public $bar = 'php';
}

$foo = new foo();

$a = $foo; // 标识符赋值(把$a赋值为null,原来的$foo并不会变成null,但通过$a能够修改$foo的成员$bar)
$a = &$foo; // 引用赋值(把$a赋值为null,原来的$foo也会跟着变成null)
$a = clone $foo; // 值赋值(赋值后互不影响,在计算机内存上的体现属于浅复制)

对象复制

在PHP中, 对象间的赋值操作实际上是引用操作 (事实上,绝大部分的编程语言都是如此! 主要原因是内存及性能的问题) , 比如 :

class myclass {
public $data;
}

$obj1 = new myclass();
$obj1->data = "aaa";
$obj2 = $obj1;
$obj2->data ="bbb"; //$obj1->data的值也会变成"bbb"

因为obj1和 obj2 都是指向同一个内存区的引用,所以修改任何一个对象都会同时修改另外一个对象。

在有些时候,我们其实不希望这种reference式的赋值方式, 我们希望能完全复制一个对象,这是侯就需要用到 Php中的clone (对象复制)。

class myclass {
public $data;
}

$obj1 = new myclass();
$obj1->data ="aaa";
$obj2 = clone $obj1;
$obj2->data ="bbb"; // $obj1->data的值仍然为"aaa"

因为clone的方式实际上是对整个对象的内存区域进行了一次复制并用新的对象变量指向新的内存, 因此赋值后的对象和源对象相互之间是基本来说独立的。

浅复制

什么? 基本独立?!这是什么意思? 因为PHP的object clone采用的是浅复制(shallow copy)的方法, 如果对象里的属性成员本身就是reference类型的,clone以后这些成员并没有被真正复制,仍然是引用的。 (事实上,其他大部分语言也是这样实现的, 如果你对C++的内存,拷贝,copy constructor等概念比较熟悉,就很容易理解这个概念), 下面是一个例子来说明:

class myClass{
public $data;
}

$sss ="aaa";
$obj1 = new myClass();
$obj1->data =&$sss; //注意,这里是个reference!
$obj2 = clone $obj1;
$obj2->data="bbb"; //这时,$obj1->data的值变成了"bbb" 而不是"aaa"!

var_dump($obj1);
var_dump($obj2);

我们再举一个更实用的例子来说明一下PHP clone这种浅复制带来的后果:

class testClass{
public $str_data;
public $obj_data;
}

$dateTimeObj = new DateTime("2014-07-05", new DateTimeZone("UTC"));

$obj1 = new testClass();
$obj1->str_data ="aaa";
$obj1->obj_data = $dateTimeObj;

$obj2 = clone $obj1;

var_dump($obj1); // str_data:"aaa" obj_data:"2014-07-05 00:00:00"
var_dump($obj2); // str_data:"aaa" obj_data:"2014-07-05 00:00:00"

$obj2->str_data ="bbb";
$obj2->obj_data->add(new DateInterval('P10D')); //给$obj2->obj_date 的时间增加了10天

var_dump($obj1); // str_data:"aaa" obj_data:"2014-07-15 00:00:00" !!!!
var_dump($obj2); // str_data:"bbb" obj_data:"2014-07-15 00:00:00"
var_dump($dateTimeObj) // 2014-07-15 00:00:00"

这一下可以更加清楚的看到问题了吧。 一般来讲,你用clone来复制对象,希望是把两个对象彻底分开,不希望他们之间有任何关联, 但由于clone的shallow copy的特性, 有时候会出现非你期望的结果.

深复制

实际上是同一个内存区对象数据的引用,因此修改其中任何一个都会影响其他两个!

如何解决这个问题呢? 采用PHP中的 __clone方法 把浅复制转换为深复制(这个方法给C++中的copy constructor概念上有些相似,但执行流程并不一样)

class testClass{
public $str_data;
public $obj_data;
public function __clone() {
$this->obj_data = clone $this->obj_data;
}
}

$dateTimeObj = new DateTime("2014-07-05", new DateTimeZone("UTC"));

$obj1 = new testClass();
$obj1->str_data ="aaa";
$obj1->obj_data = $dateTimeObj;

$obj2 = clone $obj1;
var_dump($obj1); // str_data:"aaa" obj_data:"2014-07-05 00:00:00"
var_dump($obj2); // str_data:"aaa" obj_data:"2014-07-05 00:00:00"
$obj2->str_data ="bbb";
$obj2->obj_data->add(new DateInterval('P10D'));

var_dump($obj1); // str_data:"aaa" obj_data:"2014-07-05 00:00:00"
var_dump($obj2); // str_data:"aaa" obj_data:"2014-07-15 00:00:00"
var_dump($dateTimeObj); //"2014-07-05 00:00:00"