Home  >  Article  >  Backend Development  >  PHP中”单例模式“实例讲解

PHP中”单例模式“实例讲解

WBOY
WBOYOriginal
2016-06-23 13:42:58893browse

 

假设我们需要写一个类用来操作数据库,并同时满足以下要求:

①SqlHelper类只能有一个实例(不能多)
②SqlHelper类必须能够自行创建这个实例
③必须自行向整个系统提供这个实例,换句话说:多个对象共享一块内存区域,比如,对象A设置了某些属性值,则对象B,C也可以访问这些属性值(结尾的例子很好的说明了这个问题)

 1 <?php 2     class SqlHelper{ 3         private static $_instance; 4         public $_dbname; 5         private function __construct(){ 6              7         } 8         public function getDbName(){ 9             echo $this->_dbname;10         }11         public function setDbName($dbname){12             $this->_dbname=$dbname;13         }14         public function clear(){15             unset($this->_dbname);16         }17         18     }19     $sqlHelper=new SqlHelper();//打印:Fatal error: Call to private SqlHelper::__construct() from invalid context 20 ?>

以上的SqlHelper类是无法从自身的类外部创建实例的,因为我们将构造函数设为了private,所以通过new SqlHelper()是无法从类外部使用私有的构造函数的,如果强制使用,将会报如下错误:
Fatal error: Call to private SqlHelper::__construct() from invalid context
严重错误:从上下文中调用了一个私有的构造函数SqlHelper::__construct()

按照已往的思维逻辑,实例化一个类都是直接在类外部使用new操作符的,但是既然这里讲构造函数设为private了,我们知道,私有的成员属性或函数只能在类的内部被访问,所以我们可以通过在类SqlHelper内部再创建一个函数(比如:getInstance()),而且必须是public的,getInstance()函数中主要进行的是实例化SqlHelper类
比如:

 1 <?php 2     class SqlHelper{ 3         private $_instance; 4         //......省略 5         public function getInstance(){ 6             $this->_instance=new SqlHelper(); 7         } 8         //......省略 9     }10 ?>

但是问题出现了,
①我们在调用getInstance()之前没有实例化SqlHelper对象,所以也就无法通过对象的方式来调用getInstance()函数了,
②既然在调用getInstance的时候还未实例化出对象,所以在getInstance函数中使用$this肯定也会报错(Fatal error: Using $this when not in object context)
那如何解决呢?

解决途径:我们可以讲getInstance()方法设为静态的,根据静态的定义,她只能被类而不是对象调用,将$_instance也设为静态的即可。所以这个方法正好符合我们的口味。
所以我们进一步将代码修改如下:

 1 <?php 2     class SqlHelper{ 3         private static $_instance; 4         private function __construct(){ 5             echo "构造函数被调用"; 6         }         7         //......省略 8         public static function getInstance(){ 9             if (self::$_instance===null) {10 //                self::$_instance=new SqlHelper();//方式一11                 self::$_instance=new self();//方式二                12             }13             return self::$_instance;14         }15         //......省略16     }17     $sqlHelper=SqlHelper::getInstance();//打印:构造函数被调用18 ?>

通过在getInstance函数中对当前内存中有误存在当类类的一个实例进行判断,如果没有则实例化,并返回对象句柄,如果有则直接返回该对象句柄
至此,完整代码如下所示:

 1 <?php 2     class SqlHelper{ 3         private static $_instance; 4         public $_dbname; 5         private function __construct(){ 6              7         } 8         //getInstance()方法必须设置为公有的,必须调用此方法 9         public static function getInstance(){10             //对象方法不能访问普通的对象属性,所以$_instance需要设为静态的11             if (self::$_instance===null) {12 //                self::$_instance=new SqlHelper();//方式一    13                 self::$_instance=new self();//方式二        14             }15             return self::$_instance;16         }17         public function getDbName(){18             echo $this->_dbname;19         }20         public function setDbName($dbname){21             $this->_dbname=$dbname;22         }23     }24 //    $sqlHelper=new SqlHelper();//打印:Fatal error: Call to private SqlHelper::__construct() from invalid context 25     $A=SqlHelper::getInstance();26     $A->setDbName('数据库名');27     $A->getDbName();28 //    unset($A);//移除引用29     $B=SqlHelper::getInstance();30     $B->getDbName();31     $C=SqlHelper::getInstance();32     $C->getDbName();33     34 ?>

以上代码的执行结果:
数据库名//$A->getDbName();

数据库名//$B->getDbName();
数据库名//$C->getDbName();
也就是说,对象A,B,C实际上都是使用同一个对象实例,访问的都是同一块内存区域
所以,即使unset($A),对象B和C还是照样能够通过getDbName()方法输出“数据库名”的
unset($A)实际上只是将对象A与某块内存地址(该对象的实例所在的地址)之间的联系方式断开而已,跟对象B和对象C无关,可以用用一张图表示如下

 

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn