
PHP 单例模式讲解和代码示例
单例是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。
单例拥有与全局变量相同的优缺点。 尽管它们非常有用, 但却会破坏代码的模块化特性。
在某些其他上下文中, 你不能使用依赖于单例的类。 你也将必须使用单例类。 绝大多数情况下, 该限制会在创建单元测试时出现。
复杂度:
流行度:
使用示例: 许多开发者将单例模式视为一种反模式。 因此它在 PHP 代码中的使用频率正在逐步减少。
识别方法: 单例可以通过返回相同缓存对象的静态构建方法来识别。
概念示例
本例说明了单例设计模式的结构并重点回答了下面的问题:
- 它由哪些类组成?
- 这些类扮演了哪些角色?
- 模式中的各个元素会以何种方式相互关联?
了解该模式的结构后, 你可以更容易地理解下面基于真实世界的 PHP 应用案例。
index.php: 概念示例
<?php namespace RefactoringGuru\Singleton\Conceptual; /** * The Singleton class defines the `GetInstance` method that serves as an * alternative to constructor and lets clients access the same instance of this * class over and over. */ class Singleton { /** * The Singleton's instance is stored in a static field. This field is an * array, because we'll allow our Singleton to have subclasses. Each item in * this array will be an instance of a specific Singleton's subclass. You'll * see how this works in a moment. */ private static $instances = []; /** * The Singleton's constructor should always be private to prevent direct * construction calls with the `new` operator. */ protected function __construct() { } /** * Singletons should not be cloneable. */ protected function __clone() { } /** * Singletons should not be restorable from strings. */ public function __wakeup() { throw new \Exception("Cannot unserialize a singleton."); } /** * This is the static method that controls the access to the singleton * instance. On the first run, it creates a singleton object and places it * into the static field. On subsequent runs, it returns the client existing * object stored in the static field. * * This implementation lets you subclass the Singleton class while keeping * just one instance of each subclass around. */ public static function getInstance(): Singleton { $cls = static::class; if (!isset(self::$instances[$cls])) { self::$instances[$cls] = new static(); } return self::$instances[$cls]; } /** * Finally, any singleton should define some business logic, which can be * executed on its instance. */ public function someBusinessLogic() { // ... } } /** * The client code. */ function clientCode() { $s1 = Singleton::getInstance(); $s2 = Singleton::getInstance(); if ($s1 === $s2) { echo "Singleton works, both variables contain the same instance."; } else { echo "Singleton failed, variables contain different instances."; } } clientCode();
Output.txt: 执行结果
Singleton works, both variables contain the same instance.
真实世界示例
单例模式由于限制了代码复用, 且让单元测试复杂化而名声不佳。 但它在有些情况下仍然非常实用, 特别是在需要控制一些共享资源时十分方便。 例如, 全局日志对象必须对日志文件的访问权限进行控制。 另一个例子: 共享的运行时配置存储。
index.php: 真实世界示例
<?php namespace RefactoringGuru\Singleton\RealWorld; /** * If you need to support several types of Singletons in your app, you can * define the basic features of the Singleton in a base class, while moving the * actual business logic (like logging) to subclasses. */ class Singleton { /** * The actual singleton's instance almost always resides inside a static * field. In this case, the static field is an array, where each subclass of * the Singleton stores its own instance. */ private static $instances = []; /** * Singleton's constructor should not be public. However, it can't be * private either if we want to allow subclassing. */ protected function __construct() { } /** * Cloning and unserialization are not permitted for singletons. */ protected function __clone() { } public function __wakeup() { throw new \Exception("Cannot unserialize singleton"); } /** * The method you use to get the Singleton's instance. */ public static function getInstance() { $subclass = static::class; if (!isset(self::$instances[$subclass])) { // Note that here we use the "static" keyword instead of the actual // class name. In this context, the "static" keyword means "the name // of the current class". That detail is important because when the // method is called on the subclass, we want an instance of that // subclass to be created here. self::$instances[$subclass] = new static(); } return self::$instances[$subclass]; } } /** * The logging class is the most known and praised use of the Singleton pattern. * In most cases, you need a single logging object that writes to a single log * file (control over shared resource). You also need a convenient way to access * that instance from any context of your app (global access point). */ class Logger extends Singleton { /** * A file pointer resource of the log file. */ private $fileHandle; /** * Since the Singleton's constructor is called only once, just a single file * resource is opened at all times. * * Note, for the sake of simplicity, we open the console stream instead of * the actual file here. */ protected function __construct() { $this->fileHandle = fopen('php://stdout', 'w'); } /** * Write a log entry to the opened file resource. */ public function writeLog(string $message): void { $date = date('Y-m-d'); fwrite($this->fileHandle, "$date: $message\n"); } /** * Just a handy shortcut to reduce the amount of code needed to log messages * from the client code. */ public static function log(string $message): void { $logger = static::getInstance(); $logger->writeLog($message); } } /** * Applying the Singleton pattern to the configuration storage is also a common * practice. Often you need to access application configurations from a lot of * different places of the program. Singleton gives you that comfort. */ class Config extends Singleton { private $hashmap = []; public function getValue(string $key): string { return $this->hashmap[$key]; } public function setValue(string $key, string $value): void { $this->hashmap[$key] = $value; } } /** * The client code. */ Logger::log("Started!"); // Compare values of Logger singleton. $l1 = Logger::getInstance(); $l2 = Logger::getInstance(); if ($l1 === $l2) { Logger::log("Logger has a single instance."); } else { Logger::log("Loggers are different."); } // Check how Config singleton saves data... $config1 = Config::getInstance(); $login = "test_login"; $password = "test_password"; $config1->setValue("login", $login); $config1->setValue("password", $password); // ...and restores it. $config2 = Config::getInstance(); if ( $login == $config2->getValue("login") && $password == $config2->getValue("password") ) { Logger::log("Config singleton also works fine."); } Logger::log("Finished!");
Output.txt: 执行结果
2018-06-04: Started! 2018-06-04: Logger has a single instance. 2018-06-04: Config singleton also works fine. 2018-06-04: Finished!