單例模式是一種 design pattern,要求一個 Class 只能有一個實例,通常會使用在系統中需要一個全局物件的情況。
在實作時的邏輯就是:「一個 Class 只能有一個儲存自身實例化的靜態變數 $instance
、和一個獲得這個變數的靜態方法 getInstance()
」
實際在使用時會 call getInstance()
這個靜態方法,他會檢查 $instance
是否為空,若不為空則回傳,若為空則實例化 Class 賦予 $instance
並回傳。
<?php
final class Singleton
{
private static $instance;
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new Singleton;
}
return self::$instance;
}
public $_somethingWeWantToKeep = null;
}
我們用 final
宣告 class 來確保不會被繼承,並如同上面所說的,建立 static function 來 return 一個存了 class 實體化後物件的 static 變數,最後在 class 中宣告一個變數 $_somethingWeWantToKeep
。
實際上在使用的情況是這樣的:
Singleton::getInstance()->_somethingWeWantToKeep = 'I\'m smart!';
之後我們在任何地方都可以引用 Singleton::getInstance()->_somethingWeWantToKeep
,並且都會得到一樣的 I'm smart!
在上面的例子中, Singleton 大致上完成了,但還不夠嚴謹。
舉一個簡單的例子:
Singleton::getInstance()->_somethingWeWantToKeep = 'I\'m smart!';
var_dump(Singleton::getInstance()->_somethingWeWantToKeep); //I'm smart!
$stupidGuy = new Singleton;
$stupidGuy->getInstance()->_somethingWeWantToKeep = 'I\'m stupid!';
var_dump(Singleton::getInstance()->_somethingWeWantToKeep); //I'm stupid!
上述的情況簡單來說就是在 Singleton 之後,被外部調用並重新賦值,所以我們可以在 class 中新增:
private function __construct() {}
覆寫 __construct()
來避免 Singleton 被外部調用。
還有可能出現另一種情況:
Singleton::getInstance()->_somethingWeWantToKeep = 'I\'m smart!';
var_dump(Singleton::getInstance()->_somethingWeWantToKeep); //I'm smart!
$stupidGuy = clone Singleton::getInstance();
$stupidGuy->getInstance()->_somethingWeWantToKeep = 'I\'m stupid!';
var_dump(Singleton::getInstance()->_somethingWeWantToKeep); //I'm stupid!
這個地方也是發生類似的事情,我們的 Singleton 被 clone 之後調用了 getInstance()
,同樣的我們
private function __clone() {}
覆寫 __clone
來避免 Singleton 被 clone 。
最後還有一個可能是序列化(serialize)
Singleton::getInstance()->_somethingWeWantToKeep = 'I\'m smart!';
var_dump(Singleton::getInstance()->_somethingWeWantToKeep); //I'm smart!
$stupidGuy = serialize(Singleton::getInstance());
$stupidGuy = unserialize($stupidGuy);
$stupidGuy->getInstance()->_somethingWeWantToKeep = 'I\'m stupid!';
var_dump(Singleton::getInstance()->_somethingWeWantToKeep); //I'm stupid!
相對應的防範措施是
private function __wakeup() {}
所以完整的 Singleton 如下:
<?php
final class Singleton
{
private static $instance;
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new Singleton;
}
return self::$instance;
}
public $_somethingWeWantToKeep = null;
private function __construct() {}
private function __clone() {}
private function __wakeup() {}
}
總之我們做的事情就是防止這位 stupidGuy 對這個應該要是單例的 Class 建了物件,然後還呼叫 static function 來修改我們想要保持不變的東西,也就是在其他開發者在不知情的情況下,建立了副本並修改時,噴 error 給他。
事實上 Singleton pattern 因為隱藏依賴和難以測試,被廣泛地認為是一種 anti-pattern,可以參考在 stackoverflow 的討論。
什麼是 Git? Git 是一種分散式版本控制系統。 版本控制系統? 未導入版本控制系統之前,我們對專案進行修改或整合時,必須耗費大量的人力來對程式碼做備份和比對的工作。這在專案規模逐漸擴大或是多人協作的場景時,每個人的程式碼進度不一,哪段程式碼被修改或覆蓋?這段程式碼和上一次的版本有什麼不同?導致程式碼混亂不堪,難以維護,而版本控制系統就是要解決這樣的問題。 分散式? 版本控制主要分為集中式(Centralized Version Control Systems,CVCSs)和分散式(Distributed Version Control Systems, DVCSs)兩種,前者如 SVN,後者如 Git。 在 Git 中,每個協作者都會擁有一個自己完整的版本庫,你可以在自己的版本庫中盡情的開 branch、修改程式碼,只要不 push 到主要的版本庫,你不會影響任何一個與你協作的夥伴。
Jun 20, 2020什麼是 Composer? Composer 是 PHP 用來管理套件的工具,是現代 PHPer 必備的武器之一。 為什麼我們需要 Composer? 在沒有使用 Composer 的情況下,每使用一個套件就必須 include 一次,套件又可能相依其他套件,於是我們又要再 include 那個套件。 若是自己開發的小專案還沒關係,要是專案一大起來,可能會有兩種情況,一種是不同的情況需要不同的套件,於是 include 就散落在各個檔案中,難以管理;另一種就是在程式進入點一次 include 一大堆,include 相連到天邊。
May 10, 2020or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up