這5種最常見的設計模式裡面沒有單例模式(Singleton),很多人很討厭這種模式。那麼這五種設計模式到底是哪些呢?
工廠模式
(抽象工廠、工廠方法、簡單工廠)
工廠模式存在的意義,在於用new關鍵詞例項化一個物件之後還遠遠不能滿足要求,更重要的是對其進行初始化賦值。比如說微服務當中,需要建立一個http client例項呼叫另一個微服務API。僅僅例項化一個GuzzleHttp\Client肯定是不夠的,比如你還需要配置url和header(比如說jwt token),這樣一來每次需要傳送請求的時候,可以拿來即用,而不是每次都重新配置。
其次值得注意的是,設計模式提倡的是應該依賴抽象類或者介面去程式設計,在依賴注入的時候才使用實現類,保持最大的靈活性和可維護性。
比如一個使用者類(Client)需要寫日誌,日誌有幾種選擇,可以寫到檔案系統、Redis或者資料庫。
interface LoggerFactory { public function create (): Logger; }interface Logger{ public function log ();}class RedisLogger implements Logger { private RedisClient $client; public function __construct (RedisClient $client) { $this->client = $client; } public function log ($msg, $context) { //save log to redis server }}class RedisLoggerFactory implements LoggerFactory { public function create (): Logger { $redisClient = ... // 此處是一段比較複雜的操作 return new RedisLogger ($redisClient); }}
LoggerFactory是工廠方法介面,而Logger則是模式裡的產品介面。RedisLoggerFactory是具體工廠方法類,而RedisLogger則是具體產品類。
策略模式(Strategy)
這個模式昨天寫過,請閱讀《PHP版Gof23:策略(Strategy)模式——把一族演算法封裝在不同子類裡》
介面卡模式(Adapter)
介面卡模式有物件介面卡和類介面卡兩種。下面這種情況應該屬於物件介面卡模式,具體的表現是介面卡類關聯適配者(Adaptee)類。
class Storage { private $source; public function __constructor(AdapterInterface $source) { $this->source = $source; } public function getOne(int $id) : ?object { return $this->source->find($id); } public function getAll(array $criteria = []) : Collection { return $this->source->findAll($criteria); }}interface AdapterInterface { public function find(int $id) : ?object; public function findAll(array $criteria = []) : Collection;}class MySqlAdapter implements AdapterInterface { // ... 上下文 public function find(int $id) : ?object { $data = $this->mysql->fetchRow(['id' => $id]); } public function findAll(array $criteria = []) : Collection { $data = $this->mysql->fetchAll($criteria); } // ... 上下文}
介面卡模式的應用場景:我們有兩個(介面或者)類,一個是目標類(Target),另一個是適配者類。這兩種類(或者介面)都不可被修改,目標類需要呼叫適配者類的方法,但是二者不相容。打個比方說,中國碼工帶著筆記本去歐洲出差,遇到了插座和筆記本不相容的問題。很明顯,我們不能改變膝上型電腦,也不能改變歐洲的電源插口,怎麼辦呢?只能買一個轉接頭,讓中國的筆記本能夠接通歐洲的電源。如果你財力雄厚,毫不猶豫地在本地買了一個新筆記本,這種情況下介面卡模式就失去了意義。
在上面的例子裡,Target是AdapterInterface,它定義了find和findAll兩個介面方法。適配者類是某種mysql類,它沒有find和findAll這兩個函式,因此產生了不相容的問題。因此例子中採用了物件介面卡的思路,MySqlAdapter實現了AdapterInterface (也就是目標介面),同時關聯了這種mysql類,問題也得到了解決。
觀察者模式(Observer)
觀察者模式又稱為釋出/訂閱模式,這個模式的應用非常非常廣泛,而且威力非常大。在wordpress裡面的鉤子機制,可以透過add_action增加一個鉤子,然後在do_action處呼叫。
我們來看一個簡單一點的例子。
class Theater { public function present(Movie $movie) : void { $this->getEventManager() ->notify(new Event(Event::START, $movie)); $movie->play(); $movie->pause(5); $this->getEventManager() ->notify(new Event(Event::PAUSE, $movie)); $movie->finish(); $this->getEventManager() ->notify(new Event(Event::END, $movie)); }}$theater = new Theater();$theater ->getEventManager() ->listen(Event::START, new MessagesListener()) ->listen(Event::START, new LightsListener()) ->listen(Event::PAUSE, new BreakListener()) ->listen(Event::PAUSE, new AdvertisementListener()) ->listen(Event::END, new FeedbackListener()) ->listen(Event::END, new CleaningListener()); $theater->present($movie);
這裡的Event::{START|PAUSE|END}就相當於WP_Hook中的tag。而notify這個方法就類似於WP_Hook裡面的do_action。
在觀察者模式之前,所有的監聽器需要被顯性的關聯到目標類裡。這樣一來可擴充套件性就大大降低了,而顯性關聯的類也和目標類建立了耦合關係。觀察者模式中,這種顯性的關聯解除了,因此耦合關係就不存在了。
裝飾模式(Decor)
裝飾模式是一種結構型模式,目的是在動態的給同一基類的派生類增加額外的功能。
初學者可能對“動態的”這件事情會感到很迷惑,下面我們用一個例項來聊一下“動態的”是什麼意思。
<?phpabstract class Burger { abstract public function getPrice () : float; abstract public function getIngredients () : array; public function __toString () : string { return 'A burger with ' . implode (', ', $this->getIngredients ()) . ', the total price is '.$this->getPrice (); }}class BasicBurger extends Burger{ private $price = 10.0; private $ingredients = ['bread', 'patty']; public function getPrice () : float { return $this->price; } public function getIngredients () : array { return $this->ingredients; }}abstract class BurgerAddon extends Burger { protected Burger $burger; public function __construct (Burger $burger) { $this->burger = $burger; } abstract public function getPrice () : float; abstract public function getIngredients () : array;}class OnionBurgerAddon extends BurgerAddon{ public function getPrice () : float { return $this->burger->getPrice () + 1.5; } public function getIngredients () : array { return array_merge($this->burger->getIngredients (), ['onion']); }}class MushroomBurgerAddon extends BurgerAddon{ public function getPrice () : float { return $this->burger->getPrice () + 3.5; } public function getIngredients () : array { return array_merge($this->burger->getIngredients (), ['mushroom']); }} $burger = new BasicBurger ();$burger = new OnionBurgerAddon ($burger);$burger = new MushroomBurgerAddon ($burger);echo $burger;// 輸出結果:A burger with bread, patty, onion, mushroom, the total price is 15?>
裝飾模式和代理模式有一些相似,區別也是比較明顯的。裝飾模式會對被裝飾的類的職能進行加強,比如上面的例子當中,透過兩層裝飾,漢堡增加了洋蔥和烤蘑菇,職能(responsibility)得到了加強。但是代理模式對被代理的類的職能不做任何修改。
參考:https://medium.com/@ivorobioff/the-5-most-common-design-patterns-in-php-applications-7f33b6b7d8d6
本文在原文基礎上做了比較大的修改。