单件模式要解决的问题就是“如何让这个类只有一个实例”。
我们的web应用中,大量使用了数据库连接,如果反复建立与数据库的连接必然消耗更多的系统资源。
我们如何解决这个问题,建立唯一的数据库连接是必要的方式。
我们又如何知道与这个数据库的连接是否已经建立? 还是需要现在建立?
单件模式可以解决这个问题。
先假设我们需要一个类完成在内存中只有一份的功能,我们该如何做呢?
我们一步一步的使用前面学过的知识来写一个单件的例子。
前面学过,每次用 new 类名 的方式,就可以创建一个对象。我们必须禁止外部程序用 new 类名的方式来创建多个实例。
解决办法是:我们将构造函数设置成 private ,让构造函数只能在内部被调用,而外部不能调用。这样,这个类就不能被外部用 new 的方式建立多个实例了。
以下是不能被外部用new实例化的类。
1 | <? | |
2 | class |
3 | | |
4 | } |
5 | | |
6 | $a |
7 | ?> |
程序运行结果为:
1 | Fatal error: Call to |
我们已经禁止外部用new实例化这个类,我们改如何让用户访问这个类呢?前门堵了,我们需要给用户留个后门。
解决办法是:static 修饰的方法,可以不经实例化一个类就可以直接访问这个方法。
01 | <? | |
02 | class |
03 | | |
04 | |
05 | | |
06 | |
07 | | |
08 | } |
09 | | |
10 | echo |
11 | ?> |
程序运行结果为:
1 | 这里是后门,可以通过这里进入类内部.. |
虽然我们已经进入类内部,但我们要的是这个类的唯一实例?先不管别的,我们先需要一个实例。通过这个static的方法返回这个实例,如何做呢?
解决办法是:private的构造函数,不能被外部实例化。但是我们已经成功潜入类的内部了(间谍?007?),我们在内部当然可以调用private的方法创建对象。我们这样做看看。
下面的例子我们确实返回了A类的实例,但注意两次执行返回的不是同一个实例。
01 | //不能用new实例化的类.<br> | |
02 | //static的方法留给外部访问.<br> |
03 | //在方法内部返回实例.<br><br> | |
04 | <? |
05 | class | |
06 | |
07 | | |
08 | |
09 | | |
10 | |
11 | } | |
12 | // 看到这里确实返回的是 A 的实例.但不是同一个对象. |
13 | $a1 |
14 | $a2 |
15 | echo | |
16 | if($a1 |
17 | | |
18 | }else{ |
19 | | |
20 | } |
21 | ?> |
程序运行结果为:
1 | //不能用new实例化的类. | |
2 | //static的方法留给外部访问. |
3 | //在方法内部返回实例. | |
4 | |
5 | $a1 | |
6 | $a1 |
我们已经通过static方法返回了A的实例。但还有问题。我们如何保证我们多次操作获得的是同一个实例的呢?
解决办法:static的属性在内部也只有一个。static 属性能有效的被静态方法调用。将这个属性也设置成private,以防止外部调用。先将这个属性设置成 null。每次返回对象前,先判断这个属性是否为 null 。如果为 null 就创建这个类的新实例,并赋值给这个 static 属性。如果不为空,就返回这个指向实例的 static 属性。
01 | //不能用new实例化的类.<br> | |
02 | //static的方法留给外部访问.<br> |
03 | //在方法内部返回实例.<br> | |
04 | //定义静态属性保证这个实例能被静态方法调用.<br> |
05 | //增加判断部分.<br><br> | |
06 | <? |
07 | class | |
08 | |
09 | | |
10 | |
11 | | |
12 | |
13 | | |
14 | |
15 | | |
16 | } |
17 | // 看到这里确实返回的是 A 的实例.但不是同一个对象. | |
18 | $a1 |
19 | $a2 | |
20 | echo |
21 | if($a1 | |
22 | |
23 | }else{ | |
24 | |
25 | } | |
26 | ?> |
程序运行结果为:
1 | //不能用new实例化的类. | |
2 | //static的方法留给外部访问. |
3 | //在方法内部返回实例. | |
4 | //定义静态属性保证这个实例能被静态方法调用. |
5 | //增加判断部分. | |
6 | |
7 | $a1 | |
8 | $a1 |
到此,我们写了一个最简单的 单件模式 。
现在,你可以尝试写一个应用单件设计模式的数据库连接类。
要记住单件模式的使用效果和书写方式。
一个单例模式的例子
01 | class | |
02 | |
03 | | |
04 | |
05 | | |
06 | |
07 | | |
08 | |
09 | | |
10 | |
11 | | |
12 | |
13 | | |
14 | |
15 | | |
16 | } |
使用 test::getInstance() 来获取对象。无论获取多少次,返回的都是同一个对象。不会额外的去重复创建自己的复本。大大的节省了系统资源的浪费。
假设我们需要写一个类用来操作数据库,并同时满足以下要求:
①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无关,可以用用一张图表示如下
原创文章:MarcoFly
转载请注明出处:http://www.cnblogs.com/hongfei/archive/2012/07/07/2580994.html