一文搞懂│工廠模式、單例模式、策略模式、介面卡模式、觀察者模式的原理和使用

極客飛兔發表於2022-07-13

? 工廠模式

  • 工廠模式的原理
  • 作用: 就是你只要傳你需要的類進去,你就能得到他的例項化物件
  • 其實工廠就是幫你例項化你所需要的類
<?php
/**
* 工廠類
*/
class factory
{
    public static function create($className) {
        return new $className();
    }
}
 
class A {}
class B {}
 
$a = factory::create('A');
$b = factory::create('B');
 
var_dump($a); // object(A)#1 (0) {}
var_dump($b); // object(B)#2 (0) {}
  • 工廠模式的應用
  • 例項化多個類來處理不同業務時候使用,這裡以求矩形和圓形的周長和麵積為案例
<?php
/**
 * Interface shape
 */
interface shape
{
    public function area();
    public function perimeter();
}
 
/**
 * 矩形
 * Class rectangle
 */
class rectangle implements shape
{
    private $width;
    private $height;
 
    public function __construct($width, $height) {
        $this->width = $width;
        $this->height = $height;
    }
 
    public function area() {
        return $this->width * $this->height;
    }
 
    public function perimeter() {
        return 2 * ($this->width + $this->height);
    }
}
 
/**
 * 圓
 * Class circle
 */
class circle implements shape
{
    private $radius;
 
    public function __construct($radius) {
        $this->radius = $radius;
    }
 
    public function area() {
        return M_PI * pow($this->radius, 2);
    }
 
    public function perimeter() {
        return 2 * M_PI * $this->radius;
    }
}
 
/**
 * 工廠類
 * Class factory
 */
class factory
{
    public static function create() {
        switch (func_num_args()) {
            case 1:
                return new circle(func_get_arg(0));
                break;
            case 2:
                return new rectangle(func_get_arg(0), func_get_arg(1));
                break;
        }
    }
}
 
$a = factory::create(4, 5);
$b = factory::create(2);
 
echo '矩形的周長為:' . $a->perimeter();
echo '矩形的面積為:' . $a->area();
echo '圓的周長為:' . $a->perimeter();
echo '圓的面積為:' . $a->area();

? 單例模式

  • 單例模式的原理
  • 作用: 當你例項化多次類的時候,讓其只存在在唯一的記憶體空間中,減少資源的消耗
  • 普通類的例項化,一個 new 將會建立一個例項化記憶體空間,因為空間不同,這將會導致系統記憶體開銷增大
  • 但是同一個類,功能都一樣,沒必要放在不同的記憶體空間中
<?php
/**
 * Class A
 */
class A {}
 
$a = new A();
$b = new A();

// 非單例模式中可以看到其中#1,#2分屬不同的記憶體空間
var_dump($a); // object(A)#1 (0) {}
var_dump($b); // object(A)#2 (0) {}
  • 單例模式的定義
  • 單例模式的入門口訣是:三私一公
  • 私有的構造方法: 防止人為外部使用 new 進行建立這就是上面普通內的例項化了
  • 私有的克隆方法: 防止人為外部使用 clone 方法後進行例項化
  • 私有的靜態屬性: 用來儲存單一的例項化物件
  • 公有的靜態方法: 用來實現單一的例項化邏輯
  • 從結果來看:兩個類的物件記憶體空間都指向了 #1,實現了單例模式的基礎構建
<?php
/**
 * Class database
 */
class database
{
    /**
     * @var $instance
     */
    private static $instance;
 
    /**
     * 私有的構造方法,禁止外部例項化
     * database constructor.
     */
    private function __construct() {}
 
    /**
     * 私有的克隆方法,禁止外部克隆
     */
    private function __clone() {}
 
    /**
     * 獲取單例
     * @return database
     */
    public static function getInstance()
    {
        if(!self::$instance instanceof self) {
            self::$instance = new self();
        }
 
        return self::$instance;
    }
}
 
$a = database::getInstance();
$b = database::getInstance();

var_dump($a); // object(database)#1 (0) {}
var_dump($b); // object(database)#1 (0) {}
  • 單例模式的應用
  • 其實在專案中單例模式的應用很多,無非就是有些東西只需要例項化一個物件就行了,不需要多次進行例項化
  • 這其中的應用場景常見的就包括PDO連線資料庫,Redis的連線等等
<?php
/**
 * Class mysql
 */
class mysql
{
    /**
     * @var \PDO
     */
    private $pdo;
 
    /**
     * @var $instance
     */
    private static $instance;
 
    /**
     * @var array
     */
    private $_config = [
        'host' => '127.0.0.1',
        'post' => 3306,
        'user' => 'root',
        'password' => '',
        'charset' => 'utf8',
        'dbname' => 'autofelix',
        'except' => 'PDO::ERRMODE_EXCEPTION'
    ];
 
    /**
     * mysql constructor.
     */
    private function __construct() {}
 
    /**
     * 資料庫連結
     */
    public function connect()
    {
        try {
            $dsn = "mysql:host={$this->_config['host']};port={$this->_config['post']};dbname={$this->_config['dbname']};charset={$this->_config['charset']}";
            $this->pdo = new PDO($dsn, $this->_config['user'], $this->_config['password']);
            $this->pdo->setAttribute(PDO::ATTR_ERRMODE, $this->_config['except']);
        } catch (PDOException $e) {
            exit('資料庫連線失敗:' . $e->getMessage());
        }
    }
 
    /**
     * @param $sql
     * @return array
     */
    public function getAll($sql)
    {
        $this->sql = $sql;
        $pdostatement = $this->pdo->query($sql);
        return $pdostatement->fetchAll(PDO::FETCH_ASSOC);
    }
 
    /**
     * @return mysql
     */
    public static function getInstance()
    {
        if(!self::$instance instanceof self) {
            self::$instance = new self();
        }
        return self::$instance;
    }
 
    private function __clone() {}
}
 
$mysql = mysql::getInstance();
$mysql->connect();
 
$sql = 'select * from autofelix_users where 1';
$result = $mysql->getAll($sql);
echo json_encode($result);

? 策略模式

  • 策略模式的原理
  • 作用: 比如你去淘寶上買東西,如果你是男生,它的首頁會給你推薦男生喜歡的物品,如果你是女生呢,它會給你推薦女生常用的物品,策略模式其實就是給物件進行分類
  • 由上面可知,程式設計中的策略模式,就是會知道你是什麼人,然後給你推薦你喜歡的東西,讓營銷最大化
  • 這裡必然涉及到,程式在執行的時候,給你這個人進行分門別類,然後執行了不同的方法導致的
  • 這裡我們定義兩個類,擁有相同的方法,執行的內容卻不同
  • 策略模式需要做的就是當使用者進來時候,同一個入口讓他根據這個人的行為去執行其中某一個類中的方法
<?php
/**
 * Class A
 */
class A {
    public function name()
    {
        echo "我是A類";
    }
}
 
/**
 * Class B
 */
class B {
    public function name()
    {
        echo "我是B類";
    }
}
 
/**
 * Class strategy
 */
class strategy
{
    /**
     * @var $obj
     */
    private $obj;
 
    /**
     * @return mixed
     */
    public function getName()
    {
        return $this->obj->name();
    }
 
    /**
     * @param $class
     */
    public function setClass($class)
    {
        $this->obj = $class;
    }
}
 
$strategy = new strategy();
// 分門別類
// $strategy->setClass(new A());
$strategy->setClass(new B());
// 同一個入口
$strategy->getName(); // 我是B類
  • 策略模式的應用
  • 情景: 一個使用者去某酒店網站定住宿為例,頁面上根據你的歷史消費記錄,會為你顯示高等住宿和豐富的晚餐,或者僅僅顯示大眾住宿和廉價的自助餐
  • 我們先定義介面去實現住房和晚餐的方法
  • 然後定義兩個群裡的類去實現這個介面,分別是尊貴的人群和普通的人群
  • 當有個autofelix使用者去訂房間,給他注入大眾使用者的類
<?php
/**
 * 定義介面
 * Interface userStrategy
 */
interface userStrategy
{
    public function hotel();
    public function dinner();
}
 
/**
 * 尊貴的客人享有的待遇
 * Class rich
 */
class rich implements userStrategy
{
    public function hotel()
    {
        return "你是高貴的客人,為你推薦了高階住宿";
    }
 
    public function dinner()
    {
        return "你是高貴的客人,為你推薦了燭光晚餐";
    }
}
 
/**
 * 普通的客人享有的待遇
 * Class poor
 */
class poor implements userStrategy
{
    public function hotel()
    {
        return "你是普通的客人,為你推薦了大眾住宿";
    }
 
    public function dinner()
    {
        return "你是普通的客人,為你推薦了自助餐";
    }
}
 
/**
 * Class user
 */
class user
{
    private $_userClass;
 
    public function getHotel() {
        return $this->_userClass->hotel();
    }
 
    public function getDinner() {
        return $this->_userClass->dinner();
    }
 
    public function setUserClass(userStrategy $userStrategy) {
        $this->_userClass = $userStrategy;
    }
}
 
/**
 * 這時候有個autofelix使用者過來網站預定房間
 * Class autofelix
 */
class autofelix extends user {}
 
$people = new autofelix();
 
//設定群體
$people->setUserClass(new poor());
 
//獲取該群體的住宿和晚餐
$hotel = $people->getHotel();
$dinner = $people->getDinner();
 
echo json_encode([
    'hotel' => $hotel,
    'dinner' => $dinner
]);

// 結果如下
{
    hotel: "你是普通的客人,為你推薦了大眾住宿",
    dinner: "你是普通的客人,為你推薦了自助餐"
}

? 介面卡模式

  • 介面卡模式的原理
  • 作用: 將一個類的介面轉換成客戶希望的另一個介面,介面卡模式使得原本的由於介面不相容而不能一起工作的那些類可以一起工作
  • 比如:在某個場景中,老專案寫了很多介面公你呼叫,但突然有一天,上司說要換個介面方法名呼叫,需要你用另一個方法名去實現相同的功能
  • 你是直接改後端程式碼的方法名稱?這肯定行不通,因為專案不止你這一個地方呼叫這個介面,一旦修改,其他地方就崩了,還是去重新複製那段邏輯程式碼,改個名字,這樣不是不行,只是寫了重複程式碼,顯得臃腫了
<?php
 
class A
{
    private $str;
 
    public function __construct($str)
    {
        $this->str = $str;
    }
 
    public function getStr()
    {
        return $this->str;
    }
 
    // 錯誤示範,直接複製 getStr 中的程式碼改個方法名,臃腫
    public function getString()
    {
        return $this->str;
    }
}
 
//介面卡模式前
$a = new A('i am autofelix');
$result = $a->getStr();
var_dump($result);
  • 介面卡模式的應用
  • 而正確的常見,應該是使用介面卡模式處理這類問題
  • 通過定義統一介面,然後通過實現介面去實現
<?php
// 專案原本程式碼
class A
{
    private $str;
 
    public function __construct($str)
    {
        $this->str = $str;
    }
 
    public function getStr()
    {
        return $this->str;
    }
}

// 定義統一介面
interface AInterface {
    function getString();
}
 
class B implements AInterface
{
    /**
     * @var A
     */
    private $_A;
 
    public function __construct($class)
    {
        $this->_A = $class;
    }
 
    public function getString()
    {
        return $this->_A->getStr();
    }
}
 
// 介面卡模式前
$a = new A('i am autofelix');
$result = $a->getStr();
var_dump($result);

// 介面卡模式後
$b = new B($a);
$result = $b->getString();
var_dump($result);

? 觀察者模式

  • 觀察者模式的原理
  • 作用: 用來監控使用者的某些操作,然後根據使用者這些操作來處理一些後續的事情
  • 舉個例子:一個使用者去網上購買電影票,付款成功後,系統需要發簡訊給使用者,順便記錄使用者購票的日誌等其他多個邏輯操作
// 系統自帶的觀察者介面
// 預設需要實現 onListen 和 getObserverName 這兩個方法
// 如果是自定義觀察者介面名,一定要實現onListen同功能的方法
// onListen 註冊監聽行為
interface InterfaceObserver
{
    public function onListen($sender, $args);
    public function getObserverName();
}

// 定義可被觀察者的介面
// 其實就是用來監聽事件的發生
// addObserver 方法我們是用來依賴注入一些使用者購票之後系統的行為操作
// removeObserver 方法,是用來移除某個後續操作的,我們暫時不去實現
interface InterfaceObservable
{
    public function addObserver($observer);
    public function removeObserver($observer_name);
}
  • 觀察者模式的應用
  • 這裡以使用者購票後需要給使用者傳送資訊和記錄購票日誌
<?php
/**
 * Interface InterfaceObserver
 * 觀察者介面
 */
interface InterfaceObserver
{
    public function onListen($sender, $args);
    public function getObserverName();
}
 
/**
 * Interface InterfaceObservable
 * 被觀察物件介面
 */
interface InterfaceObservable
{
    public function addObserver($observer);
    public function removeObserver($observer_name);
}
 
class Ticket implements InterfaceObservable
{
    /**
     * @var array
     */
    private $_observers = [];
 
    /**
     * @param $observer
     */
    public function addObserver($observer)
    {
        if ($observer instanceof InterfaceObserver) {
            $this->_observers[] = $observer;
        }
    }
 
    /**
     * @param $observer_name
     */
    public function removeObserver($observer_name) {}
 
    /**
     * 使用者購票行為
     */
    public function buy()
    {
        //使用者購票邏輯,這裡不詳細說明,僅僅以引數代之
        $result = [
            'code' => 200,
            'msg' => '使用者購票成功',
            'sign' => 'ea5070bec29826cc0f8e0b7b6861fd75'
        ];
 
        //購票成功,開始後期處理
        if($result['code'] == 200) {
            foreach ($this->_observers as $observer) {
                $observer->onListen($this, $result['sign']);
            }
        }
    }
}
 
 
/**
 * 記錄使用者購票日誌
 * Class TicketRecord
 */
class ticketRecord implements InterfaceObserver
{
    public function onListen($sender, $args)
    {
        echo "記錄使用者購票成功,編號為:{$args}<br/>";
    }
 
    public function getObserverName() {}
}
 
/**
 * 給使用者傳送觀影簡訊
 * Class sendMsg
 */
class sendMsg implements InterfaceObserver
{
    public function onListen($sender, $args)
    {
        echo "您的電影票購買成功,請憑編號:{$args}觀影<br/>";
    }
 
    public function getObserverName() {}
}
 
$ticket = new Ticket();
$ticket->addObserver(new ticketRecord());
$ticket->addObserver(new sendMsg());
$ticket->buy();

相關文章