定义
- 保证一个类只有一个实例,并提供一个访问它的全局访问点。
应用场景
- 多人协同开发,要调用mysql类的实例对象,应避免大量的new 操作消耗的资源
- 数据库的链接,很耗资源,现在网站的瓶颈有两个,第一个是带宽,第二个是数据库。数据库的链接原则,能不连就不连
- 如何限制,让多人开发,无论你怎么操作,只能得到一个对象呢?
- 解决:使用单例模式,确保一个类只有一个实例
主要思路
- 保护或私有构造函数,防止外部实例化
- 内部开放一个公共的静态方法,负责实例化
- 类内有一个静态私有属性存放对象
- 检测,若静态私有属性已经存放对象,直接return该属性
实现历程
第一步:一个普通的类
- 这个普通类,可以new来实例化,显然不是单例
- 解决思路:不让new了
class Single{
function __construct(){
}
}
$s1 = new Single();
$s2 = new Single();
第二步:既然是new实例化了多个对象,那就不让new了
- 把构造方法保护/私有
- 确实外部不能new了,但是得不到对象了,这不叫单例,叫0例,哈哈~
- 解决思路:写一个静态方法,在方法内部new对象,在类内调用构造方法
class Single{
private function __construct(){
}
}
$s1 = new Single(); // 报错 私有方法不能在类{}外调用
第三步:通过static方法在类内部new对象
- 写一个获取实例对象的方法,return对象
- 如何调用这个方法,只能把这个方法设置为静态公共(因为要在类外静态调用)
- 虽从内部new,但仍然生成了不同对象,如何控制只能new一次
- 解决思路:写一个静态属性instance,把内部new到的对象return到instance存起来,然后做判断
class Single{
public $hash; // 随机码
private function __construct(){
$this->hash = mt_rand(1,99999);
}
static public function getInstance(){
return new Single();
}
}
$s1 = Single::getInstance();
$s2 = Single::getInstance();
print_r($s1); // [hash] => 12904
print_r($s2); // [hash] => 99339
if ($s1 === $s2){
echo '是同一个对象';
}else{
echo '不是一个对象'; // 不是一个对象
}
第四步:通过static方法在类内部new对象,并且把对象保存到类内部的静态属性
- 写一个静态私有属性,将对象保存到该属性(只能静态调用,并要防止在类外修改该属性)
- 判断
$instance
是否是Single
类的实例,来控制是否new对象,确保只有一个对象 - 至此:一个简陋的单例就做好了
class Single{
public $hash; // 随机码
static private $instance = null;
private function __construct(){
$this->hash = mt_rand(1,99999);
}
static public function getInstance(){
if (! self::$instance instanceof Single){ // instanceof判断某个对象是不是某个类的实例
self::$instance = new Single();
}return self::$instance;
}
}
$s1 = Single::getInstance();
$s2 = Single::getInstance();
print_r($s1); // [hash] => 6556
print_r($s2); // [hash] => 6556
if ($s1 === $s2){
echo '是同一个对象'; // 是同一个对象
}else{
echo '不是一个对象';
}
漏洞1 : 辛苦写好的单例 继承一下 就不灵了
- 继承,然后再写一个同名公共构造函数,又可以new对象了
- 父类的构造函数为私有,由父类的标志,不是不能重写吗?
- 子类中
public function __construct()
,这不是在重写,子类也没权限重写,这是写了个同名的构造函数 - 解决方法:用final修饰父类构造函数 能继承 不能重写
class Single{
public $hash; // 随机码
static private $instance = null;
private function __construct(){
$this->hash = mt_rand(1,99999);
}
static public function getInstance(){
if (! self::$instance instanceof Single){ // instanceof判断某个对象是不是某个类的实例
self::$instance = new Single();
}return self::$instance;
}
}
$s1 = Single::getInstance();
$s2 = Single::getInstance();
print_r($s1); // [hash] => 43232
print_r($s2); // [hash] => 43232
class Test extends Single{
public function __construct(){
}
}
$t1 = new Test();
$t2 = new Test();
print_r($t1); // [hash] => 6524
print_r($t2); // [hash] => 4332
解决1:用final修饰父类构造函数 能继承 不能重写
- final关键字修饰的类方法 能继承 不能重写
- s1 s2 t1 t2 指向同一个对象
echo '<pre>';
class Single{
public $hash; // 随机码
static private $instance = null;
final private function __construct(){
$this->hash = mt_rand(1,99999);
}
static public function getInstance(){
if (! self::$instance instanceof Single){ // instanceof判断某个对象是不是某个类的实例
self::$instance = new Single();
}return self::$instance;
}
}
$s1 = Single::getInstance();
$s2 = Single::getInstance();
print_r($s1); // [hash] => 24726
print_r($s2); // [hash] => 24726
class Test extends Single{
}
$t1 = Single::getInstance();
$t2 = Single::getInstance();
print_r($t1); // [hash] => 24726
print_r($t2); // [hash] => 24726
漏洞2:问题一种的漏洞解决了是吗?那我clone一下
$t3 = clone $t2
,把$t2
对象完全复制一份,存到一个新开辟的内存中- s1 s2 t1 t2 指向同一个对象,t3 指向新复制的对象
- 这不是有2个对象了吗?
echo '<pre>';
class Single{
public $hash; // 随机码
static private $instance = null;
final private function __construct(){
$this->hash = mt_rand(1,99999);
}
static public function getInstance(){
if (! self::$instance instanceof Single){ // instanceof判断某个对象是不是某个类的实例
self::$instance = new Single();
}return self::$instance;
}
}
$s1 = Single::getInstance();
$s2 = Single::getInstance();
print_r($s1); // [hash] => 24726
print_r($s2); // [hash] => 24726
class Test extends Single{
}
$t1 = Single::getInstance();
$t2 = Single::getInstance();
print_r($t1); // [hash] => 24726
print_r($t2); // [hash] => 24726
$t3 = clone $t2;
print_r($t3); // [hash] => 24726 这里[hash]值一样的原因是 clone $t3对象时,[hash]已经有值
if ($t3 === $t2){
echo '是同一个对象';
}else{
echo '不是一个对象'; // 不是一个对象
}
解决2:防止使用clone克隆对象
- 使用魔术方法
function __clone(){}
防止使用clone克隆对象
单例模式最终版
类内使用self代替类名
// 单例模式最终版
class Single{
// 创建静态私有的变量保存该类对象
static private $instance = null;
// 防止使用new直接创建对象
final private function __construct(){
}
static public function getInstance(){
// 判断$instance是否是Singleton的对象,不是则创建
if (! self::$instance instanceof self){ // instanceof判断某个对象是不是某个类的实例
self::$instance = new self(); // self代替类名更推荐
}return self::$instance;
}
// 防止使用clone克隆对象 设为私有
private function __clone(){
}
}
$s1 = Single::getInstance();
$s2 = Single::getInstance();
单例练习
单例数据库
class DaoMysql{
private $mysql_link;
private static $instance = null;
final private function __construct($host,$user,$pwd){
$this->mysql_link = mysqli_connect($host,$user,$pwd);
}
public static function getInstance($host,$user,$pwd){
if (!self::$instance inStanceof self){
self::$instance = new self($host,$user,$pwd);
}return self::$instance;
}
private function __clone(){}
}
$d1 = DaoMysql::getInstance('localhost','root','');
$d2 = DaoMysql::getInstance('localhost','root','');
var_dump($d1,$d2);
内存图分析
$instance和p1 p2指向同一个对象标识符