一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

浅析php单态设计模式之单例模式的理解

时间:2022-06-24 22:54:07 编辑:袖梨 来源:一聚教程网

1.单态设计模式含义:

单态模式的主要作用是保证在面向对象编程设计中,一个类只能有一个实例对象存在。作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统全局地提供这个实例。它不会创建实例副本,而是会向单例类内部存储的实例返回一个引用。

2.单台模式的三个关键点:

① 需要一个保存类的唯一实例的静态成员变量;
②构造函数和克隆函数必须声明为私有的,防止外部程序new类从而失去单例模式的意义;
③必须提供一个访问这个实例的公共的静态方法(通常为getInstance方法),从而返回唯一实例的一个引用 。

 

 代码如下 复制代码
class DB {
private static $obj = null;           //声明一个私有的,静态的成员属性$obj
private function__construct() {       //私有构造方法,只能在类的内部实例化对象
echo "连接数据库成功
";
}
public static function getInstance() {  // 通过此静态方法才能获取本类的对象
if(is_null(self::$obj))  //如果本类中的$obj为空,说明还没有被实例化过
self::$obj = new self();  //实例化本类对象
return self::$obj;  //返回本类的对象
}
public function query($sql) {  //执行SQL语句完成对数据库的操作
echo $sql;
}
}
$db = DB::getInstance();        //只能使用静态方法getInstance()去获取DB类的对象
$db -> query("select *from user");       //访问对象中的成员
?>

 

单例模式,就是保持一个对象只存在一个实例。并且为该唯一实例提供一个全局访问点(一般是一个静态的getInstance方法)。单例模式应用场景非常广泛,例如:
数据库操作对象
日志写入对象
全局配置解析对象等
这些场景的共同特征是从业务逻辑上来看运行期间改对象却是

只需要一个实例

不断new多个实例会增加不必要的资源消耗

全局调用便利

下面分别说明这三个方面:

1. 业务上只需要一个实例

以数据库连接对象为例,加入有一个购物网站,有一个MySQL数据库
127.0.0.1:3306
,那么在一个进程中无论我们需要进行多少次针对改数据库的操作,都只需要连接数据库一次,使用相同的数据库连接句柄(MySQL Connection Resource),从业务需求上来看就只需要一个实例。

相反,同样以购物网站为例,存在许多商品,这些商品都不一样(id,name,price..),这个时候需要显示一个商品列表,加入我们建立一个
Product
作为数据映射对象,那么从业务需求上来说,一个实例就无法满足业务需求,因为每个商品都不一样。

2. 不断new操作增加不必要的资源消耗


我们一般会在类的构造方法(new操作肯定会调用)中进行一些业务操作,例如数据库连接对象可能会在构造方法中尝试读取数据库配置并进行数据库连接(如mysqli::__construct())、日志写入对象会判断日志写入目录是否存在并写入(不存在可能尝试创建改目录)、全局配置解析对象可能需要定位配置文件的保存目录并进行文件扫描等。

这些业务都会消耗相当的资源,如果在一个进程中我们值需要做一次,将会非常有利于我们提高应用的运行效率。

3. 全局调用便利

因为单例模式的一大特点就是通过静态方法获取对象实例,那么就意味着访问对象的方法时不需要先new一个对象的实例,如果改对象需要很多地方使用,则提高了调用的便利性。

通过一个日志操作类来举例:

 代码如下 复制代码
class Logger{

    //首先,需要一个私有的静态变量来存储产生的对象实例
    private static $instance;

    //业务变量,保存日志写入路径
    private $logDir;

    //构造方法,注意必须也是私有的,不允许被外部实例化(即在外部被new)
    private function __construct(){
        //调试输出,测试对象被new的次数
        echo "new Logger instance rn";
        $logDir = sys_get_temp_dir(). DIRECTORY_SEPARATOR . "logs";
        if(!is_dir($logDir) || !file_exists($logDir)){
            @mkdir($logDir);
        }
        $this->logDir = $logDir;

    }

    //类唯一实例的全局访问点,用于判断并返回对象实例,供外部调用
    public static function getInstance(){
        if(is_null(self::$instance)){
            $class = __CLASS__; //获取本对象的类型,也可以用new self()方式
            self::$instance = new $class();
        }
        return self::$instance;
    }

    //重载__clone()方法,不允许对象对克隆
    public function __clone(){
        throw new Exception("Singleton Class Can Not Be Cloned");
    }

    //具体的业务方法,实际可以有很多方法
    public function logError($message){
        $logFile = $this->logDir . DIRECTORY_SEPARATOR . "error.log";
        error_log($message, 3, $logFile);
    }
}

//日志调用
$logger = Logger::getInstance();
$logger->logError("An error occured");
$logger->logError("Another error occured");

//或者这样调用
Logger::getInstance()->logError("Still have error");
Logger::getInstance()->logError("I should fix it");

在单例模式中可能遇到一种比较特殊的情况,比如数据库连接对象,对于大型应用来说,很可能需要连接多台数据库,那么不同的数据库公用一个对象可能会产生问题,比如连接的分配、获取
insert_id

last_error
等。这个问题也比较好解决,就是把我们的$instance变量变成一个关联数组,通过给getInstance方法传入不同的参数获取不同的"单例对象"(引号的含义是:严格来说类可能被new多次,但是这个new也是在我们的控制之内的,而不是在类外部):

 代码如下 复制代码
class MysqlServer{
    //注意,变成复数了哦^_^  当然只是为了标识而已
    private static $instances = array();

    //业务变量,保持当前实例的mysqli对象
    private $conn;

    //显著特征:私有的构造方法,避免在类外部被实例化
    private function __construct($host, $username, $password, $dbname, $port){
        $this->conn = new mysqli($host, $username, $password, $dbname, $port);
    }

    //类唯一实例的全局访问点
    public static function getInstance($host='localhost', $username='root', $password='123456', $dbname='mydb', $port='3306'){
        $key = "{$host}:{$port}:{$username}:{$dbname}";
        if (empty(self::$instances[$key])){
            //这里也可以用 new self(); 的方式
            $class = __CLASS__;
            self::$instances[$key] = new $class($host, $username, $password, $dbname, $port);
        }
        return self::$instances[$key];
    }

    //重载__clone方法,不允许对象实例被克隆
    public function __clone(){
        throw new Exception("Singleton Class Can Not Be Cloned");
    }

    //查询业务方法,后面省略其它业务方法
    public function query($sql){
        return $this->conn->query($sql);
    }

    //尽早释放资源
    public function __destruct(){
        $this->conn->close();
    }
}

问题1:单例类能否拥有子类,因为单例类的构造方法是私有的,因此无法被继承,如果要继承则需要将构造方法改为protected或public,这就违背了单例模式的本意。因此,如果你想给单例类加子类,那就需要回头想想是否错用了模式,或者结构设计上有问题。

问题2:单例滥用,单例模式相对来说比较好理解和实现,因此一旦认识到单例模式的好处,很可能什么类都想写成单例,因此在使用次模式之前一定要考虑上述3种情况,看是否真的有必要使用。

热门栏目