Yii2設計模式——工廠方法模式

来源:https://www.cnblogs.com/minirice/archive/2019/01/02/10207105.html
-Advertisement-
Play Games

工廠方法模式(Factory Method Pattern)定義了一個創建對象的介面,但由子類決定要實例化的類是哪一個。工廠方法讓類吧實例化推遲到子類。 ...


應用舉例

yii\db\Schema抽象類中:

//獲取數據表元數據
public function getTableSchema($name, $refresh = false)
{
    if (array_key_exists($name, $this->_tables) && !$refresh) {
        return $this->_tables[$name];
    }

    $db = $this->db;
    $realName = $this->getRawTableName($name);

    if ($db->enableSchemaCache && !in_array($name, $db->schemaCacheExclude, true)) {
        /* @var $cache Cache */
        $cache = is_string($db->schemaCache) ? Yii::$app->get($db->schemaCache, false) : $db->schemaCache;
        if ($cache instanceof Cache) {
            $key = $this->getCacheKey($name);
            if ($refresh || ($table = $cache->get($key)) === false) {
                //通過工廠方法loadTableSchema()去獲取TableSchema實例
                $this->_tables[$name] = $table = $this->loadTableSchema($realName);
                if ($table !== null) {
                    $cache->set($key, $table, $db->schemaCacheDuration, new TagDependency([
                        'tags' => $this->getCacheTag(),
                    ]));
                }
            } else {
                $this->_tables[$name] = $table;
            }

            return $this->_tables[$name];
        }
    }
    //通過工廠方法loadTableSchema()去獲取TableSchema實例
    return $this->_tables[$name] = $this->loadTableSchema($realName);
}

//獲取TableSchema實例,讓子類去實現
abstract protected function loadTableSchema($name);

這裡使用了工廠方法模式。loadTableSchema()就是工廠方法,它負責提供一個具體的TableSchema類以供getTableSchema()使用。而要提供具體的TableSchema類,顯然要到各個Schema的子類中去實現。

工廠方法模式

模式定義

工廠方法模式(Factory Method Pattern)定義了一個創建對象的介面,但由子類決定要實例化的類是哪一個。工廠方法讓類吧實例化推遲到子類。

什麼意思?說起來有這麼幾個要點:

  • 對象不是直接new產生,而是交給一個類方法去完成。比如loadTableSchema()方法

  • 這個方法是抽象的,且必須被子類所實現
  • 這個提供實例的抽象方法需要參與到其他邏輯中,去完成另一項功能。比如loadTableSchema()方法出現在getTableSchema()方法中,參與實現獲取數據表元數據的功能

代碼實現

現在,我們打算用利用面向對象的手段——工廠方法模式為你開一家披薩連鎖店。

假如你現在打算開一家披薩連鎖店,分店以加盟的方式加入。加盟店提供加盟費以及利潤提成,而總店提供配方,烹飪方法,以及選址和其他方面的建議。這種場景夠常見了吧?什麼奶茶加盟店、花甲加盟店、火鍋加盟店等等都是這個套路。那麼,現在你打算怎麼做?

首先,我希望為各個加盟店制定一定的規範以保證基本口味和品牌形象,比如配方、原料、加工方式都我來提供

其次,我也允許加盟店可以適當的擴展以增加其靈活性和豐富性,比如切塊和包裝可以採用自己的

如果你想明白這兩點,那恭喜你!你已經基本搞清楚了抽象和具體的關係了。在這裡總店就是是抽象,加盟店就是具體

第一步,我們的披薩是什麼樣子的,配方原料加工方式是什麼。因此我們要有個抽象的Pizza類,規定要做披薩的原料有麵團、醬汁、各種配菜;加工程式有準備、烘烤、切塊、包裝。

abstract class Pizza
{
    /**
     * @var
     */
    protected $name;
    /**
     * @var
     */
    protected $dough;
    /**
     * @var
     */
    protected $sauce;
    /**
     * @var array
     */
    protected $toppings = [];

    public function prepare()
    {
        print_r('Preparing '.$this->name.'<br>');
        print_r('Tossing dough...'.'<br>');
        print_r('Adding sauce...'.'<br>');
        print_r('Adding toppings...'.'<br>');
        foreach ($this->toppings as $topping) {
            print_r("$topping".'<br>');
        }
    }

    public function bake()
    {
        print_r('Bake: Bake for 25 minutes at 350'.'<br>');
    }

    public function cut()
    {
        print_r('Cut: Cutting the pizza into diagonal slices'.'<br>');
    }

    public function box()
    {
        print_r('Box: Place pizza in official PizzaStore box'.'<br>');
    }

    /**
     * @return mixed
     */
    public function getName()
    {
        return $this->name;
    }
}

你是總店,你可以規定哪些工序可以用我總店的,而哪些必須你自己去實現。比如,加盟店選擇開在哪裡必須加盟店自己去完成,而原料和做法則總店來決定。

體現在上面的代碼,就是abstract Pizza類所有方法都可以根據需要改成abstract的。當你要求必須子類去完成的就是abstract的;當你提供了預設的行為,可讓子類繼承直接使用的就是具體的。

規定了披薩如何構成的,總店第二步還需要指導下分店怎麼把披薩做出來,因此還需要有個抽象的披薩店類PizzaStore:

abstract class PizzaStore
{
    /**
     * @var Pizza
     */
    protected $pizza;

    /**
     * @param $type
     *
     * @return Pizza
     */
    public function orderPizza($type)
    {
        // create a pizza
        $this->pizza = $this->createPizza($type);

        // handle the pizza
        $this->pizza->prepare();
        $this->pizza->bake();
        $this->pizza->cut();
        $this->pizza->box();

        // return the prepared pizza
        return $this->pizza;
    }

    /**
     * Create a Pizza.
     *
     * @param $type
     *
     * @return Pizza
     */
    abstract protected function createPizza($type);
}

總店在抽象的PizzaStore規定了披薩加工的基本流程,所有的加盟店加工披薩都必須按照准備->烘烤->切塊->裝盒這幾個固定的工序進行。至於誰提供披薩,這些披薩原料是啥,烘烤多久,切成啥形狀,包裝成啥樣子,這些都是具體的Pizza本身所的細節,由各個加盟店自己決定的。

因此,各個加盟店必須要繼承抽象的createPizza()方法,去具體實現自己的細節,做出不同口味的披薩來。

最後一步,我們可以讓別人來加盟了。

有人打算在紐約開一家披薩加盟店:

class NYPizzaStore extends PizzaStore
{
    /**
     * Create a Pizza.
     *
     * @param $type
     *
     * @return Pizza
     */
    public function createPizza($type)
    {
        if ($type == 'cheese') {
            return new NYStyleCheesePizza();
        } elseif ( $type == 'clam') {
            return new NYStyleClamPizza();
        }
    }
}

紐約店暫時提供兩種口味的披薩:乳酪味和蛤蜊味。

//乳酪味
class NYStyleCheesePizza extends Pizza
{
    /**
     * NYStyleCheesePizza constructor.
     */
    public function __construct()
    {
        $this->name = 'NY Style Sauce and Cheese Pizza';
        $this->dough = 'Thin Crust Dough';
        $this->sauce = 'Marinara Sauce';

        $this->toppings[] = 'Grated Reggiano Cheese';
    }

    /**
     * {@inheritdoc}
     */
    public function box()
    {
        print_r('Box: Place pizza in NY PizzaStore box'.'<br>');
    }
}

//蛤蜊味
class NYStyleClamPizza extends Pizza
{
    /**
     * NYStyleClamPizza constructor.
     */
    public function __construct()
    {
        $this->name = 'NY Style Sauce and Clam Pizza';
        $this->dough = 'Thin Crust Dough';
        $this->sauce = 'Marinara Sauce';

        $this->toppings[] = 'Grated Reggiano Clam';
    }

    /**
     * {@inheritdoc}
     */
    public function box()
    {
        print_r('Box: Place pizza in NY PizzaStore box'.'<br>');
    }
}

紐約店兩種口味披薩的特點是:

  • 乳酪味:薄麵團、Marinara醬料,配菜是Grated Reggiano Cheese(一種乳酪),採用自己的包裝,烘烤和切塊採用總店的

  • 蛤蜊味:薄麵團、Marinara醬料,配菜是Grated Reggiano Clam(一種蛤蜊),採用自己的包裝,烘烤和切塊採用總店的

看來紐約喜歡的披薩麵團要薄一點....

不久,芝加哥又想加盟一家披薩店,和紐約人不同的是,芝加哥人希望披薩的麵團厚一些,所謂一方一俗吧。於是我們把店先開起來,然後再做披薩:

class ChicagoPizzaStore extends PizzaStore
{
    /**
     * @param $type
     *
     * @return Pizza
     */
    public function createPizza($type)
    {
        if ($type == 'cheese') {
            return new ChicagoStyleCheesePizza();
        } elseif ($type' == 'clam) {
            return new ChicagoStyleClamPizza();
        }
    }
}

芝加哥的分店暫時也只提供兩種口味:乳酪味和蛤蜊味。

//乳酪味
class ChicagoStyleCheesePizza extends Pizza
{
    /**
     * ChicagoStyleCheesePizza constructor.
     */
    public function __construct()
    {
        $this->name = 'Chicago Style Deep Dish Cheese Pizza';
        $this->dough = 'Extra Thick Crust Dough';
        $this->sauce = 'Plum Tomato Sauce';

        $this->toppings[] = 'Shredded Mozzarella Cheese';
    }

    public function cut()
    {
        print_r('Cut: Cutting the pizza into square slices').'<br>';
    }
}

//蛤蜊味
class ChicagoStyleClamPizza extends Pizza
{
    /**
     * ChicagoStyleClamPizza constructor.
     */
    public function __construct()
    {
        $this->name = 'Chicago Style Deep Dish Clam Pizza';
        $this->dough = 'Extra Thick Crust Dough';
        $this->sauce = 'Plum Tomato Sauce';

        $this->toppings[] = 'Shredded Mozzarella Clam';
    }

    public function cut()
    {
        print_r('Cut: Cutting the pizza into square slices'.'<br>');
    }
}

芝加哥店兩種口味披薩的特點是:

  • 乳酪味:加厚麵團、Plum Tomato醬料,配菜是Shredded Mozzarella Cheese(一種乳酪),切成方塊,烘烤和包裝採用總店的

  • 蛤蜊味:加厚麵團、Marinara醬料,配菜是Shredded Mozzarella Clam(一種蛤蜊),切成方塊,烘烤和包裝採用總店的

現在我們就有了兩家分店,四種不同口味披薩了。你已經等了很久了,來吃些披薩吧:

class Test 
{
    public function run()
    {
        $nyStore = new NYPizzaStore();   
        print_r("Terry ordered a NY Style Cheese Pizza" . '<br>');
        $pizza1 = $nyStore->orderPizza('cheese');

        echo '<br><br>';

        $chicagoStore = new ChicagoPizzaStore();
        print_r("Json ordered a Chicago Style Clam Pizza" . '<br>');
        $pizza2 = $chicagoStore->orderPizza('clam');
    }
}

我想嘗嘗紐約乳酪風味的,那我首先要有個紐約店,再由紐約店給我提供乳酪味的;我想嘗嘗芝加哥蛤蜊味的,那我首先要有個芝加哥店,再有芝加哥店給我提供蛤蜊味的。

認識工廠方法模式

所有的工廠模式都是用來封裝對象的創建。工廠方法模式通過讓子類來決定創建的對象是什麼,從而達到將對象創建的過程封裝的目的。

披薩店通過orderPizza()提供最終的披薩,在orderPizza()看來,我需要一個工廠方法createPizza()給我提供一個未加工的,然後我來做準備、烘烤、切塊、包裝,最終返回。orderPizza()無需瞭解披薩具體細節,因為反正所有的披薩都這麼個過程。而在抽象的PizzaStore中也createPizza()也不確定細節,它只能保證自己要提供這麼一個。具體的細節是其子類去規定。

因此,表現在代碼中,就是在abstract class PizzaStore中,我還沒有一個Pizza的實例呢,但prepare()->bake()->cut()->box()->return 也照樣這麼做了下來。

public function orderPizza($type)
{
    // create a pizza
    $this->pizza = $this->createPizza($type);

    // handle the pizza
    $this->pizza->prepare();
    $this->pizza->bake();
    $this->pizza->cut();
    $this->pizza->box();

    // return the prepared pizza
    return $this->pizza;
}

此時的$this->pizza是實現abstract class Pizza的規定的一種抽象,而還不是一個具體的實例。

這就是依賴抽象而不依賴具體

工廠方法模式的另一種認識,就是將orderPizza()和一個工廠方法createPizza()聯合起來,加上其他的prepare()/bake()等邏輯,就組成了一個框架,一種規範。子類繼承這個抽象類也就獲得了這個規範。如果說createPizza()是子類自由發揮的部分,那麼orderPizza()就給你規定了自由發揮的一些前提,從而是有限度的自由。這是總店希望看到的,希望你在這個框框裡面去開你的分店,而不要自由發揮得太離譜。

工廠方法和簡單工廠

工廠方法和簡單工廠的區別在於,簡單工廠把全部的事情,在一個地方都處理完了,然而工廠方法卻是在創建一個框架,讓子類決定要如何實現。比如說,在工廠方法中,orderPizza()方法提供了一般的框架,以便創建披薩,orderPizza()方法依賴工廠方法創建具體類,再經過一系列其他操作,最終製造出實際的披薩。可通過繼承PizzaStore類,決定實際造出的披薩是什麼。簡單工廠的做法,可以將對象的創建封裝起來,但是一下子給你一個完整的,沒有那種子類的“推遲”,因此不具備工廠方法的彈性。

工廠方法和抽象工廠

細心的讀者可能會發現一個問題,就是各家披薩店的原料都是自己提供(參看各個披薩店的__construct()),這樣口味還是有較大的隨意性。為了保證各分店口味大致相同,我們需要對原料做統一管理,讓總店來統一供給做披薩的原料。

為了吃一個披薩,我們首先要有個披薩工廠(店),為了供給披薩原料,我們需要什麼呢?——原料工廠唄!如果想到這一層,那恭喜你,已經進階到抽象工廠的層次了。

抽象工廠將上面的dough,sauce,cheese,clams等等——凡是出現過的原料都讓一個抽象方法去實現,將所有這些抽象方法集合起來放到一個類里,就是抽象工廠。例如,原料工廠應該實現下麵的介面:

interface PizzaIngredientFactory
{
    /**
     * @return Dough
     */
    public function createDough();

    /**
     * @return Sauce
     */
    public function createSauce();

    /**
     * @return Cheese
     */
    public function createCheese();

    /**
     * @return Veggie[]
     */
    public function createVeggies();

    /**
     * @return Clams
     */
    public function createClams();
}

實現了這個介面的就是具體原料工廠。在讓PizzaStore創建披薩時,先往PizzaStore註入一個PizzaIngredientFactory實例,然後委托這個實例去提供各種原料。

因此,抽象工廠其實是基於工廠方法的。工廠方法定義創建一種產品,而抽象工廠負責定義常見一組產品的介面,這個介面的每個方法都像工廠方法一樣創建一個具體產品,同時我們用抽象工廠的子類去提供這些具體的做法,從而最終提供一組一組的形形色色的產品來。

Yii2中的工廠方法模式

我們已經走得夠遠了,讓我們回到Yii2框架中來。

本文開頭的那個例子中,yii\db\Schema是為各個DBMS提供了一個統一的、抽象的資料庫信息的基類。getTableSchema()方法是獲取表的元數據,而loadTableSchema()將一個數據表填充為一個TableSchema類。

也就說說loadTableSchema()需要返回一個TableSchema的具體類。這個類包含了schema名、表名、主鍵foreignKeys以及代表數據表欄位信息的ColumnSchema的數組colums[]。

顯然,各個資料庫的表和欄位類型是有差異的,因此loadTableSchema()必須為抽象方法,由子類去做具體實現。mysql/mssql/cubrid/sqlite等DBMS的Schema也確實繼承了yii\db\Schema類,實現了各自的loadTableSchema()具體方法。

以mysql的為例:

class Schema extends \yii\db\Schema
{
    //...

    /**
     * Loads the metadata for the specified table.
     * @param string $name table name
     * @return TableSchema driver dependent table metadata. Null if the table does not exist.
     */
    protected function loadTableSchema($name)
    {
        $table = new TableSchema;
        $this->resolveTableNames($table, $name);

        if ($this->findColumns($table)) {
            $this->findConstraints($table);

            return $table;
        } else {
            return null;
        }
    }

    //...
}

它就在loadTableSchema()方法中返回了一個具體的TableSchema實例。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 由於升級了 v0.2 版 GearCase 使用打包工具從 parcel 更換成 vue-cli 3.x。因此打包後發佈 NPM 包的方式與之前有很大的差異,這也導致了在發佈完 GearCase v0.2.2 版本之後,我自己在進行 NPM / Yarn 安裝包時。根本無法通過之前文檔的方式,進行引 ...
  • (1)typeof 和 instanceof 1、typeof 對於基本數據類型(boolean、null、undefined、number、string、symbol)來說,除了 null 都可以顯示正確的類型;對於對象來說,除了函數都會顯示 object。 2、instanceof 是通過原型鏈 ...
  • 這個問題有時候會導致彈出框確定按鈕失效等一系列問題, 解決辦法:失去焦點時將頁面滾動到底層,或者最頂部,個人看實際情況滾動到適合位置 $('input,textarea').on('blur', function () { window.scroll(0, document.body.scrollH ...
  • FaaS介紹 微服務(MicroService)是以專註於單一服務/功能的小型單元塊為基礎,利用模塊化的方式組合成複雜的大型應用服務。 FaaS是Function as a Service的縮寫,可以簡單理解為功能服務化。FaaS提供了一種比微服務更加服務碎片化的軟體架構範式。FaaS可以讓研發只需 ...
  • 閑話少說,直接列舉問題: ①AB兩台資料庫伺服器做集群,由於兩台伺服器配置不一樣,如何在代碼中要實現每訪問伺服器A的資料庫2次,才能訪問伺服器B的資料庫1次。 ②AB兩台資料庫伺服器做讀寫分離,如何在代碼中控制,查詢數據使用伺服器A的資料庫連接,增刪改使用伺服器B的資料庫連接。 代碼結構: 代碼實現 ...
  • 一、概念 組合模式:允許你將對象組合成樹形結構來表現“整體/部分”層次結構。組合能讓客戶以一致的方式處理個別對象以及對象組合。 組合包含組件。組件有兩種:組合和葉節點元素。組合持有一群孩子,這些孩子可以是別的組合或者葉節點元素。 角色:  組合部件(Component):它是一個抽象角色, ...
  • WHAT 項目中必須對應的隱性需求-安全漏洞修複 WHY 小時候下圍棋,總樂於持白子。因為我的打法是“從那裡來我哪裡堵”,在防守中尋找對方的漏洞。這種作戰方法是有底層的思想根因的:就是懶惰。不願意去主動思考佈局。 在這一思想的引導下,我目前正面臨著過去十多年積累起來的困境。記得大學之前,面對一個認識 ...
  • 如果這是第二次看到我的文章,歡迎右側掃碼訂閱我喲~ 👉 本文長度為4229字,建議閱讀11分鐘。 這是本系列中既「數據一致性」後的第二章節——「高可用」的完結篇。 前面幾篇中z哥跟你聊了聊做「高可用」的意義,以及如何做「負載均衡」和「高可用三劍客」(熔斷、限流、降級,文末會附上前文連接:))。這次 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...