싱글턴 은 같은 종류의 객체가 하나만 존재하도록 하고 다른 코드의 해당 객체에 대한 단일 접근 지점을 제공하는 생성 디자인 패턴입니다.
싱글턴은 전역 변수들과 거의 같은 장단점을 가지고 있습니다: 매우 편리하나 코드의 모듈성을 깨뜨립니다.
싱글턴에 의존하는 클래스를 다른 콘텍스트에서 사용하려면 싱글턴도 다른 콘텍스트로 전달해야 합니다. 대부분의 경우 이 제한 사항은 유닛 테스트를 생성하는 동안 발생합니다.
개념적인 예시
이 예시는 싱글턴 패턴의 구조를 보여주고 다음 질문에 중점을 둡니다:
패턴은 어떤 클래스들로 구성되어 있나요?
이 클래스들은 어떤 역할을 하나요?
패턴의 요소들은 어떻게 서로 연관되어 있나요?
이 패턴의 구조를 배우면 실제 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!