<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                設計模式系列 | 工廠方法模式 ## 概述 **工廠方法模式**是一種創建型設計模式,其在父類中提供一個創建對象的方法,允許子類決定實例化對象的類型。 ![](https://img.kancloud.cn/62/38/62382aa0e9f49186c1334c53a8488b0a_640x400.png) ## 問題 假設你正在開發一款物流管理應用。最初版本只能處理卡車運輸,因此大部分代碼都在位于名為`卡車`的類中。一段時間后,這款應用變得極受歡迎。你每天都能收到十幾次來自海運公司的請求,希望應用能夠支持海上物流功能。 ![](https://img.kancloud.cn/2d/4b/2d4b30201e9f02842f5df8abbcd1ece4_600x250.png) > 如果代碼其余部分與現有類已經存在耦合關系,那么向程序中添加新類其實并沒有那么容易。 這可是個好消息。但是代碼問題該如何處理呢?目前,大部分代碼都與`卡車`類相關。在程序中添加`輪船`類需要修改全部代碼。更糟糕的是,如果你以后需要在程序中支持另外一種運輸方式,很可能需要再次對這些代碼進行大幅修改。 最后,你將不得不編寫繁復的代碼,根據不同的運輸對象類,在應用中進行不同的處理。 ## 解決方案 工廠方法模式建議使用特殊的*工廠*方法代替對于對象構造函數的直接調用(即使用`new`運算符)。不用擔心,對象仍將通過`new`運算符創建,只是該運算符改在工廠方法中調用罷了。工廠方法返回的對象通常被稱作“產品”。 ![](https://img.kancloud.cn/6e/99/6e99765bbb16b4d2c3082d3f2c876997_620x270.png) > 子類可以修改工廠方法返回的對象類型。 乍看之下,這種更改可能毫無意義:我們只是改變了程序中調用構造函數的位置而已。但是,仔細想一下,現在你可以在子類中重寫工廠方法,從而改變其創建產品的類型。 但有一點需要注意:僅當這些產品具有共同的基類或者接口時,子類才能返回不同類型的產品,同時基類中的工廠方法還應將其返回類型聲明為這一共有接口。 ![](https://img.kancloud.cn/eb/11/eb11be65a7058f54a8dda33fe632c7f6_490x250.png) 舉例來說,?`卡車`Truck和`輪船`Ship類都必須實現`運輸`Transport接口,該接口聲明了一個名為`deliver`交付的方法。每個類都將以不同的方式實現該方法:卡車走陸路交付貨物,輪船走海路交付貨物。?`陸路運輸`Road-Logistics類中的工廠方法返回卡車對象,而`海路運輸`Sea-Logistics類則返回輪船對象。 ![](https://img.kancloud.cn/28/e0/28e0ded43e7e05a3e8481d43ebec7c8f_640x350.png) > 只要產品類實現一個共同的接口,你就可以將其對象傳遞給客戶代碼,而無需提供額外數據。 調用工廠方法的代碼(通常被稱為*客戶端*代碼)無需了解不同子類返回實際對象之間的差別。客戶端將所有產品視為抽象的`運輸`。客戶端知道所有運輸對象都提供`交付`方法,但是并不關心其具體實現方式。 ## 工廠方法模式結構 ![](https://img.kancloud.cn/48/c3/48c361245c968ba8b9cc749731a6b029_660x380.png) 1. **產品**(Product)將會對接口進行聲明。對于所有由創建者及其子類構建的對象,這些接口都是通用的。 2. **具體產品**(Concrete Products)是產品接口的不同實現。 3. **創建者**(Creator)類聲明返回產品對象的工廠方法。該方法的返回對象類型必須與產品接口相匹配。你可以將工廠方法聲明為抽象方法,強制要求每個子類以不同方式實現該方法。或者,你也可以在基礎工廠方法中返回默認產品類型。注意,盡管它的名字是創建者,但它最主要的職責并**不是**創建產品。一般來說,創建者類包含一些與產品相關的核心業務邏輯。工廠方法將這些邏輯處理從具體產品類中分離出來。打個比方,大型軟件開發公司擁有程序員培訓部門。但是,這些公司的主要工作還是編寫代碼,而非生產程序員。 4. **具體創建者**(Concrete Creators)將會重寫基礎工廠方法,使其返回不同類型的產品。注意,并不一定每次調用工廠方法都會**創建**新的實例。工廠方法也可以返回緩存、對象池或其他來源的已有對象。 ## 偽代碼 以下示例演示了如何使用**工廠方法**開發跨平臺 UI(用戶界面)組件,并同時避免客戶代碼與具體 UI 類之間的耦合。 ![](https://img.kancloud.cn/a0/2b/a02b2248ca992fba0c5be38d8213a503_640x400.png) 基礎對話框類使用不同的 UI 組件渲染窗口。在不同的操作系統下,這些組件外觀或許略有不同,但其功能保持一致。Windows 系統中的按鈕在 Linux 系統中仍然是按鈕。 如果使用工廠方法,就不需要為每種操作系統重寫對話框邏輯。如果我們聲明了一個在基本對話框類中生成按鈕的工廠方法,那么我們就可以創建一個對話框子類,并使其通過工廠方法返回 Windows 樣式按鈕。子類將繼承對話框基礎類的大部分代碼,同時在屏幕上根據 Windows 樣式渲染按鈕。 如需該模式正常工作,基礎對話框類必須使用抽象按鈕(例如基類或接口),以便將其擴展為具體按鈕。這樣一來,無論對話框中使用何種類型的按鈕,其代碼都可以正常工作。 你可以使用此方法開發其他 UI 組件。不過,每向對話框中添加一個新的工廠方法,你就離[抽象工廠](https://refactoringguru.cn/design-patterns/abstract-factory)模式更近一步。我們將在稍后談到這個模式。 ``` // 創建者類聲明的工廠方法必須返回一個產品類的對象。創建者的子類通常會提供 // 該方法的實現。 class Dialog is // 創建者還可提供一些工廠方法的默認實現。 abstract method createButton():Button // 請注意,創建者的主要職責并非是創建產品。其中通常會包含一些核心業務 // 邏輯,這些邏輯依賴于由工廠方法返回的產品對象。子類可通過重寫工廠方 // 法并使其返回不同類型的產品來間接修改業務邏輯。 method render() is // 調用工廠方法創建一個產品對象。 Button okButton = createButton() // 現在使用產品。 okButton.onClick(closeDialog) okButton.render() // 具體創建者將重寫工廠方法以改變其所返回的產品類型。 class WindowsDialog extends Dialog is method createButton():Button is return new WindowsButton() class WebDialog extends Dialog is method createButton():Button is return new HTMLButton() // 產品接口中將聲明所有具體產品都必須實現的操作。 interface Button is method render() method onClick(f) // 具體產品需提供產品接口的各種實現。 class WindowsButton implements Button is method render(a, b) is // 根據 Windows 樣式渲染按鈕。 method onClick(f) is // 綁定本地操作系統點擊事件。 class HTMLButton implements Button is method render(a, b) is // 返回一個按鈕的 HTML 表述。 method onClick(f) is // 綁定網絡瀏覽器的點擊事件。 class Application is field dialog: Dialog // 程序根據當前配置或環境設定選擇創建者的類型。 method initialize() is config = readApplicationConfigFile() if (config.OS == "Windows") then dialog = new WindowsDialog() else if (config.OS == "Web") then dialog = new WebDialog() else throw new Exception("錯誤!未知的操作系統。") // 當前客戶端代碼會與具體創建者的實例進行交互,但是必須通過其基本接口 // 進行。只要客戶端通過基本接口與創建者進行交互,你就可將任何創建者子 // 類傳遞給客戶端。 method main() is this.initialize() dialog.render() ``` ## 工廠方法模式適合應用場景 #### 當你在編寫代碼的過程中,如果無法預知對象確切類別及其依賴關系時,可使用工廠方法。 工廠方法將創建產品的代碼與實際使用產品的代碼分離,從而能在不影響其他代碼的情況下擴展產品創建部分代碼。 例如,如果需要向應用中添加一種新產品,你只需要開發新的創建者子類,然后重寫其工廠方法即可。 #### 如果你希望用戶能擴展你軟件庫或框架的內部組件,可使用工廠方法。 繼承可能是擴展軟件庫或框架默認行為的最簡單方法。但是當你使用子類替代標準組件時,框架如何辨識出該子類? 解決方案是將各框架中構造組件的代碼集中到單個工廠方法中,并在繼承該組件之外允許任何人對該方法進行重寫。 讓我們看看具體是如何實現的。假設你使用開源 UI 框架編寫自己的應用。你希望在應用中使用圓形按鈕,但是原框架僅支持矩形按鈕。你可以使用`圓形按鈕`Round-Button子類來繼承標準的`按鈕`Button類。但是,你需要告訴`UI框架`UIFramework類使用新的子類按鈕代替默認按鈕。 為了實現這個功能,你可以根據基礎框架類開發子類`圓形按鈕 UI`UIWith-Round-Buttons,并且重寫其`create-Button`創建按鈕方法。基類中的該方法返回`按鈕`對象,而你開發的子類返回`圓形按鈕`對象。現在,你就可以使用`圓形按鈕 UI`類代替`UI框架`類。就是這么簡單! #### 如果你希望復用現有對象來節省系統資源,而不是每次都重新創建對象,可使用工廠方法。 在處理大型資源密集型對象(比如數據庫連接、文件系統和網絡資源)時,你會經常碰到這種資源需求。 讓我們思考復用現有對象的方法: 1. 首先,你需要創建存儲空間來存放所有已經創建的對象。 2. 當他人請求一個對象時,程序將在對象池中搜索可用對象。 3. …然后將其返回給客戶端代碼。 4. 如果沒有可用對象,程序則創建一個新對象(并將其添加到對象池中)。 這些代碼可不少!而且它們必須位于同一處,這樣才能確保重復代碼不會污染程序。 可能最顯而易見,也是最方便的方式,就是將這些代碼放置在我們試圖重用的對象類的構造函數中。但是從定義上來講,構造函數始終返回的是**新對象**,其無法返回現有實例。 因此,你需要有一個既能夠創建新對象,又可以重用現有對象的普通方法。這聽上去和工廠方法非常相像。 ## 實現方式 1. 讓所有產品都遵循同一接口。該接口必須聲明對所有產品都有意義的方法。 2. 在創建類中添加一個空的工廠方法。該方法的返回類型必須遵循通用的產品接口。 3. 在創建者代碼中找到對于產品構造函數的所有引用。將它們依次替換為對于工廠方法的調用,同時將創建產品的代碼移入工廠方法。 你可能需要在工廠方法中添加臨時參數來控制返回的產品類型。 工廠方法的代碼看上去可能非常糟糕。其中可能會有復雜的`switch`分支運算符,用于選擇各種需要實例化的產品類。但是不要擔心,我們很快就會修復這個問題。 4. 現在,為工廠方法中的每種產品編寫一個創建者子類,然后在子類中重寫工廠方法,并將基本方法中的相關創建代碼移動到工廠方法中。 5. 如果應用中的產品類型太多,那么為每個產品創建子類并無太大必要,這時你也可以在子類中復用基類中的控制參數。 例如,設想你有以下一些層次結構的類。基類`郵件`及其子類`航空郵件`和`陸路郵件`;?`運輸`及其子類`飛機`,`卡車`和`火車`。?`航空郵件`僅使用`飛機`對象,而`陸路郵件`則會同時使用`卡車`和`火車`對象。你可以編寫一個新的子類(例如`火車郵件`)來處理這兩種情況,但是還有其他可選的方案。客戶端代碼可以給`陸路郵件`類傳遞一個參數,用于控制其希望獲得的產品。 6. 如果代碼經過上述移動后,基礎工廠方法中已經沒有任何代碼,你可以將其轉變為抽象類。如果基礎工廠方法中還有其他語句,你可以將其設置為該方法的默認行為。 ## 代碼示例 在本例中,**工廠方法**模式為創建社交網絡連接器提供接口,可用于進行登錄網絡、發帖和潛在的其他行為,而實現所有這些功能都無需客戶端代碼與特定社交網絡的特定類相耦合。 ### PHP **index.php:** ``` <?php namespace RefactoringGuru\FactoryMethod\RealWorld; /** * The Creator declares a factory method that can be used as a substitution for * the direct constructor calls of products, for instance: * * - Before: $p = new FacebookConnector(); * - After: $p = $this->getSocialNetwork; * * This allows changing the type of the product being created by * SocialNetworkPoster's subclasses. */ abstract class SocialNetworkPoster { /** * The actual factory method. Note that it returns the abstract connector. * This lets subclasses return any concrete connectors without breaking the * superclass' contract. */ abstract public function getSocialNetwork(): SocialNetworkConnector; /** * When the factory method is used inside the Creator's business logic, the * subclasses may alter the logic indirectly by returning different types of * the connector from the factory method. */ public function post($content): void { // Call the factory method to create a Product object... $network = $this->getSocialNetwork(); // ...then use it as you will. $network->logIn(); $network->createPost($content); $network->logout(); } } /** * This Concrete Creator supports Facebook. Remember that this class also * inherits the 'post' method from the parent class. Concrete Creators are the * classes that the Client actually uses. */ class FacebookPoster extends SocialNetworkPoster { private $login, $password; public function __construct(string $login, string $password) { $this->login = $login; $this->password = $password; } public function getSocialNetwork(): SocialNetworkConnector { return new FacebookConnector($this->login, $this->password); } } /** * This Concrete Creator supports LinkedIn. */ class LinkedInPoster extends SocialNetworkPoster { private $email, $password; public function __construct(string $email, string $password) { $this->email = $email; $this->password = $password; } public function getSocialNetwork(): SocialNetworkConnector { return new LinkedInConnector($this->email, $this->password); } } /** * The Product interface declares behaviors of various types of products. */ interface SocialNetworkConnector { public function logIn(): void; public function logOut(): void; public function createPost($content): void; } /** * This Concrete Product implements the Facebook API. */ class FacebookConnector implements SocialNetworkConnector { private $login, $password; public function __construct(string $login, string $password) { $this->login = $login; $this->password = $password; } public function logIn(): void { echo "Send HTTP API request to log in user $this->login with " . "password $this->password\n"; } public function logOut(): void { echo "Send HTTP API request to log out user $this->login\n"; } public function createPost($content): void { echo "Send HTTP API requests to create a post in Facebook timeline.\n"; } } /** * This Concrete Product implements the LinkedIn API. */ class LinkedInConnector implements SocialNetworkConnector { private $email, $password; public function __construct(string $email, string $password) { $this->email = $email; $this->password = $password; } public function logIn(): void { echo "Send HTTP API request to log in user $this->email with " . "password $this->password\n"; } public function logOut(): void { echo "Send HTTP API request to log out user $this->email\n"; } public function createPost($content): void { echo "Send HTTP API requests to create a post in LinkedIn timeline.\n"; } } /** * The client code can work with any subclass of SocialNetworkPoster since it * doesn't depend on concrete classes. */ function clientCode(SocialNetworkPoster $creator) { // ... $creator->post("Hello world!"); $creator->post("I had a large hamburger this morning!"); // ... } /** * During the initialization phase, the app can decide which social network it * wants to work with, create an object of the proper subclass, and pass it to * the client code. */ echo "Testing ConcreteCreator1:\n"; clientCode(new FacebookPoster("john_smith", "******")); echo "\n\n"; echo "Testing ConcreteCreator2:\n"; clientCode(new LinkedInPoster("john_smith@example.com", "******")); ``` 執行結果 ``` Testing ConcreteCreator1: Send HTTP API request to log in user john_smith with password ****** Send HTTP API requests to create a post in Facebook timeline. Send HTTP API request to log out user john_smith Send HTTP API request to log in user john_smith with password ****** Send HTTP API requests to create a post in Facebook timeline. Send HTTP API request to log out user john_smith Testing ConcreteCreator2: Send HTTP API request to log in user john_smith@example.com with password ****** Send HTTP API requests to create a post in LinkedIn timeline. Send HTTP API request to log out user john_smith@example.com Send HTTP API request to log in user john_smith@example.com with password ****** Send HTTP API requests to create a post in LinkedIn timeline. Send HTTP API request to log out user john_smith@example.com ```
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看