我们继续扩展我们的电影网站。我们准备制作一个详情页来让用户查看更加详细的电影信息。借用封装好的数据库查询,我们编写了如下代码:
<?php
// detail.php
$movieId = isset($_GET['id']) ? $_GET['id'] : 0;
if (empty($movieId)) {
exit('操作非法');
}
$db = new Connection();
$data = $db->getAll('select * from movie where movieId = :id limit 1', array($movieId));
if (!empty($data)) {
echo sprintf('<h1>%s</h1>', $data[0]['name']);
echo sprintf('<p>%s</p>', $data[0]['info']);
}
代码本身是比较简单的。但这里有个问题:列表页和详情页都通过“new Connection()”创建了一个自己的数据库链接。这是很危险的事情,对于频繁查询数据库的php来说,这么做很容易让数据库的连接过多甚至奔溃。有没有方法让“一次访问只用一个链接”呢?
当然,我们可以考虑用全局变量来保存一个数据库连接,然后让所有页面调用它。可全局变量很容易被意外覆盖。这不是一个理想的方案。
我们这里准备在连接类上新建一个静态属性,来保存这个数据库唯一的连接。使用时,我们先检测这个属性有没有被初始化(没有则初始化),然后返回这个唯一的实例。当然,我们也不希望这部分逻辑在各个业务代码中重复编写。我们使用一个静态方法(instance)来封装这个功能,于是,我们大致有了如下的代码:
class Connection
{
// 静态属性存储唯一实例
static private $_instance = '';
private $connect = '';
private function __construct()
{
$this->connection = new PDO('mysql:host=localhost;dbname=test', 'root', '');
}
// 静态方法封装唯一实例的初始化
public static function instance()
{
if (empty(self::$_instance)) {
self::$_instance = new self();
}
return self::$_instance;
}
// 屏蔽掉克隆方法
private function __clone() {}
}
通过以上技巧,我们页面的数据库访问变为:
...// 列表页
$connection = Connection::instance();
$data = $connection->getList('select * from movie limit :offset,:length', array($offset, $length));
...
...// 详情页
$connection = Connection::instance();
$data = $connection->getAll('select * from movie where movieId = :id limit 1', array($movieId));
...
以上两个数据库的访问就都只在使用连接类里静态属性保存的那个单一实例了。
这里,我们通过静态属性存储类的唯一实例,然后通过一个对外的静态方法访问这个唯一的是实例,此种处理问题的技巧,称之为“单例模式”。
当然,我们应该注意这里的命名。它可以很明确的告诉你使用的模式和在模式里的功能。比如这里的instance。而且,严格来说,我们也应该屏蔽掉一切可能创建其他新实例的途径。比如这里我们把构造函数设置为私有,比如这里我们屏蔽了魔术方法“__clone”
在《设计模式》一书中,单例模式是这样定义的:“保证一个类仅有一个实例,并提供一个访问它的全局访问点”。这种技巧,很巧妙的解决了我们实际生产中需要有且只有一个实例的问题。也是一种比较简单、常用的设计模式。