HeadFirst設計模式讀書筆記之工廠模式

来源:https://www.cnblogs.com/Lothlorien/archive/2018/11/28/10029930.html
-Advertisement-
Play Games

1. 簡單工廠 1. 你開了一家披薩店,點披薩的方法可能是這樣: 可以看到,每當你想增加一種披薩類型,就要修改代碼,添加一種if else條件.當有多個系統存在orderPizza的需求時,每個系統都要同時修改他們的代碼.因此,需要將這種實例化具體對象的代碼封裝起來. 這就是簡單工廠方法,他不算一種 ...


1. 簡單工廠

1. 你開了一家披薩店,點披薩的方法可能是這樣:

    public Pizza orderPizza(String type) {
        Pizza pizza;
        if (type.equals("芒果披薩")){
            pizza = new MangoPizza();
        }else if (type.equals("核桃披薩")){
            pizza = new WalnutPizza();
        }else if (type.equals("橙皮披薩")){
            pizza = new FlavedoPizza();
        }else {
            pizza = new StarPizza();
        }

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

可以看到,每當你想增加一種披薩類型,就要修改代碼,添加一種if else條件.當有多個系統存在orderPizza的需求時,每個系統都要同時修改他們的代碼.因此,需要將這種實例化具體對象的代碼封裝起來.

public class PizzaStore {

    SimplePizzaFactory factory;

    public PizzaStore(SimplePizzaFactory factory){
        this.factory = factory;
    }

    public Pizza orderPizza(String type) {
        Pizza pizza;
        pizza = factory.createPizza(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}
public class SimplePizzaFactory {

    public Pizza createPizza(String type){
        Pizza pizza = null;
        
        if (type.equals("芒果披薩")){
            pizza = new MangoPizza();
        }else if (type.equals("核桃披薩")){
            pizza = new WalnutPizza();
        }else if (type.equals("橙皮披薩")){
            pizza = new FlavedoPizza();
        }else {
            pizza = new StarPizza();
        }
        
        return pizza;
    }
}

這就是簡單工廠方法,他不算一種設計模式,而是一種編程習慣.其要點有二:

1. 將產品定義為抽象類,可以被覆蓋

2. 將具體的產品放到工廠類中來實例化,由工廠創建並返回給客戶,這樣便使得產品的創建邏輯可以解耦合,並增加了復用性

2. 工廠方法

1. 假設你現在有了很多加盟店,每種加盟店根據地區的差異有自己的工廠,但加盟店雖然採用工廠方法創建披薩,但其他部分卻有所不同:烘烤的做法,不切片,使用雜七雜八的盒子...

這時,你想把創建披薩的方法與每家披薩店綁定在一起,讓每家PizzaStore中都使用createPizza()創建pizza,並且這些店還可以擁有一定的製作披薩的自主權.因此,我們需要把PizzaStore和createPizza()定義成抽象的

/**
 * 抽象的PizzaStore
 */
public abstract class PizzaStore {

    public Pizza orderPizza(String type) {
        Pizza pizza;
        // 使用自己的方法做披薩
        pizza = createPizza(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

    // 將做披薩的工廠方法放到PizzaStore中,工廠方法是抽象的,所以依賴子類來處理對象的創建
    public abstract Pizza createPizza(String type);
}

2. 現在由PizzaStore的子類來具體賣披薩

public class BeijingPizzaStore extends PizzaStore {
    @Override
    public Pizza createPizza(String item) {
        if (item.equals("橙皮披薩")){
            return new FlavedoPizza();
        }else {
            return new PekingDuckPizza();
        }
    }
}
public class ChengduPizza extends PizzaStore {
    @Override
    public Pizza createPizza(String type) {
        if (type.equals("芒果披薩")){
            return new MangoPizza();
        }else {
            return new StarPizza();
        }
    }
}

現在,orderPizza()與具體的Pizza實現了一定的解耦合,決定最終Pizza對象的變成了某個具體的pizza店,而pizza店則由顧客來決定

3. 一些對工廠方法的理解:

1. 工廠方法是抽象的,所以依賴子類來創建對象,這樣便將超類的代碼與具體創建產品的代碼分割開了

2. 工廠方法必須返回一個產品,超類通常中會用到這個產品

3. 工廠方法將產品與創建者抽象為介面,使他們成為平級的存在,具體的產品創建則使用子類來交互

4. 工廠方法將產品實例化的操作推遲到子類,子類的選擇則由創建者來決定

5. 簡單工廠方法更趨向組合,工廠方法則利用抽象和繼承

6. 簡單工廠封裝了對象的創建,一個工廠處理所有的產品,但對同一個產品不可變更其做法,工廠方法則更有彈性,子類通過重寫可以更改產品的創建

3. 依賴倒置原則

下麵是一個簡單粗暴的披薩店:

/**
 * 依賴具體對象的PizzaStore
 */
public class PizzaStore {
    
    public Pizza createPizza(String style, String type){
        Pizza pizza = null;
        if (style.equals("一元檔")){
            if (type.equals("芒果披薩")){
                pizza = new MangoPizza();
            }else if (type.equals("核桃披薩")){
                pizza = new WalnutPizza();
            }
        }else if (style.equals("二元檔")){
            if (type.equals("北京烤鴨披薩")){
                pizza = new PekingDuckPizza();
            }else if (type.equals("橙皮披薩")){
                pizza = new FlavedoPizza();
            }
        }else {
            System.out.println("披薩賣完了");
        }
        
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

可以看到,這個披薩店依賴於每一個具體的披薩對象,每增加一個披薩就要修改一次代碼,增加一個依賴,可以說是非常不好用了

從圖中看到,高層組件直接依賴於低層組件而依賴倒置原則的的內容就是:

要依賴抽象,不要依賴具體類
這個原則說明不管高層還是低層組件都不應該依賴具體類,而應該依賴於抽象.如圖所示:

與之前相比高層組件PizzaStore不在直接依賴於具體的Pizza對象,而是依賴於Pizza介面,而具體的Pizza產品則由被高層組件依賴顛倒成了依賴Pizza介面,這就是一種依賴倒置.總體來看,PizzaStore和Pizza都抽象化了,不用再依賴於具體的類型,符合依賴倒置的設計原則.
有三個指導方針可以讓你更好地遵守依賴倒置原則:

  • 變數不可以持有具體類的引用
  • 不要讓類派生自具體類
  • 不要覆蓋基類中已覆蓋的方法

4. 抽象工廠方法:創建產品家族

與普通的工廠方法比較,抽象工廠方法要顯得重型得多: 工廠方法只創建一種產品,抽象工廠方法則創建一個家族的產品

1. 典型做法是創建一個工廠的介面,這個介面負責創建所有的產品家族成員,如創建一個生產調料家族的工廠:

/**
* 原料工廠的介面,實現這樣的介面,可以隨意組合產出各式各樣的調料
* 每個原料都是一個類
*/
public interface SeasoningFactory {

   Chili createChili();
   
   DouBan createDouBan();
   
   Pepper createPepper();
   
   Salt createSalt();

}
/**
 * 郫縣調料生產廠
 */
public class PiXianSeasoningFactory implements SeasoningFactory {

    /**
     *  虎皮青椒
     * @return
     */
    @Override
    public Chili createChili() {
        return new HuPiQingJiao();
    }

    /**
     * 郫縣豆瓣
     * @return
     */
    @Override
    public DouBan createDouBan() {
        return new PiXianDouBan();
    }

    /**
     * 花椒油
     * @return
     */
    @Override
    public Pepper createPepper() {
        return new HuaJiaoYou();
    }

    /**
     * 食用鹽
     * @return
     */
    @Override
    public Salt createSalt() {
        return new ShiYongYan();
    }
}
/**
 * 灌縣調料生產廠
 */
public class GuanXianSeasoningFactory implements SeasoningFactory {

    /**
     *  虎皮青椒
     * @return
     */
    @Override
    public Chili createChili() {
        return new HuPiQingJiao();
    }

    /**
     * 老乾媽豆瓣醬
     * @return
     */
    @Override
    public DouBan createDouBan() {
        return new LaoGanMaDouBanJiang();
    }

    /**
     * 花椒油
     * @return
     */
    @Override
    public Pepper createPepper() {
        return new HuaJiaoYou();
    }

    /**
     * 食用鹽
     * @return
     */
    @Override
    public Salt createSalt() {
        return new ShiYongYan();
    }
}

下麵是抽象工廠方法的類圖:

從類圖中可以看出來,抽象工廠方法有一個缺點,就是當添加了一個新產品介面時,要去抽象工廠介面中添加一個方法,這會造成一些麻煩

2. 工廠方法與抽象工廠的一些對比

相同點:

  • 都是負責創建對象
  • 都能使程式解耦,將程式從創建大量具體對象的泥潭中拉出來

不同點:

  • 工廠方法用子類來創建具體對象,利用抽象和繼承.抽象工廠包含了一大堆介面,利用組合的思想來創建對象
  • 工廠方法一般用來創建一種產品,抽象工廠則創建一族產品
  • 工廠方法功能簡單,使用輕便.抽象工廠功能強大,使用時會創建大量的類

    3. 下麵使用抽象工廠生產的原料來製作披薩

/**
 * 原料工廠的介面,實現這樣的介面,可以隨意組合產出各式各樣的調料
 * 每個原料都是一個類
 */
public interface SeasoningFactory {

    /**
     * 辣椒
     * @return
     */
    Chili createChili();

    /**
     * 豆瓣
     * @return
     */
    DouBan createDouBan();

    /**
     * 花椒
     * @return
     */
    Pepper createPepper();

    /**
     * 鹽
     * @return
     */
    Salt createSalt();

}
/**
 * 豆瓣
 */
public abstract class DouBan {
    public abstract String sayMyName();
}
public class PiXianDouBan extends DouBan {
    @Override
    public String sayMyName() {
        return "郫縣豆瓣";
    }
}
public class LaoGanMaDouBanJiang extends DouBan {
    @Override
    public String sayMyName() {
        return "老乾媽豆瓣醬";
    }
}
/**
 * 郫縣調料生產廠
 */
public class PiXianSeasoningFactory implements SeasoningFactory {

    /**
     *  虎皮青椒
     * @return
     */
    @Override
    public Chili createChili() {
        return new HuPiQingJiao();
    }

    /**
     * 郫縣豆瓣
     * @return
     */
    @Override
    public DouBan createDouBan() {
        return new PiXianDouBan();
    }

    /**
     * 花椒油
     * @return
     */
    @Override
    public Pepper createPepper() {
        return new HuaJiaoYou();
    }

    /**
     * 食用鹽
     * @return
     */
    @Override
    public Salt createSalt() {
        return new ShiYongYan();
    }
}
/**
 * 灌縣調料生產廠
 */
public class GuanXianSeasoningFactory implements SeasoningFactory {

    /**
     *  虎皮青椒
     * @return
     */
    @Override
    public Chili createChili() {
        return new HuPiQingJiao();
    }

    /**
     * 老乾媽豆瓣醬
     * @return
     */
    @Override
    public DouBan createDouBan() {
        return new LaoGanMaDouBanJiang();
    }

    /**
     * 花椒油
     * @return
     */
    @Override
    public Pepper createPepper() {
        return new HuaJiaoYou();
    }

    /**
     * 食用鹽
     * @return
     */
    @Override
    public Salt createSalt() {
        return new ShiYongYan();
    }
}
/**
 * 每一個披薩都有一組抽象工廠生產的原料
 *
 */
public abstract class Pizza {

    protected String name;

    /**
     * 辣椒
     */
    protected Chili chili;

    /**
     * 豆瓣
     */
    protected DouBan douBan;

    /**
     * 花椒
     */
    protected Pepper pepper;

    /**
     * 鹽
     */
    protected Salt salt;


    /**
     * 在這個方法中,收集抽象工廠生產的原料
     */
    public abstract void prepare();

    public void bake(){
        System.out.println("烘烤...");
    }

    public void cut(){
        System.out.println("切片...");
    }

    public void box() {
        System.out.println("包裝...");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString(){
         return "披薩製作完畢:"+name
        +"\n生產原料:"+chili.sayMyName()
                +"\t"+douBan.sayMyName()
                +"\t"+pepper.sayMyName()
                +"\t"+salt.sayMyName();

    }
}
public class ChengduPizza extends Pizza{

    // 調料工廠
    SeasoningFactory seasoningFactory;

    public ChengduPizza(SeasoningFactory seasoningFactory) {
        this.name = "舊成都披薩";
        this.seasoningFactory = seasoningFactory;
    }

    @Override
    public void prepare() {
        System.out.println("開始準備 "+getName()+":");
        chili = seasoningFactory.createChili();
        douBan = seasoningFactory.createDouBan();
        pepper = seasoningFactory.createPepper();
        salt = seasoningFactory.createSalt();
        System.out.println("以下原料準備完畢:"+chili.sayMyName()
        +","+douBan.sayMyName()+","+pepper.sayMyName()+","+salt.sayMyName());
    }
}
public abstract class PizzaStore {

    public abstract Pizza createPizza(String type);
}
public class StarBoundPizzaStore extends PizzaStore {
    @Override
    public Pizza createPizza(String type) {
        Pizza pizza;
        SeasoningFactory factoryA = new PiXianSeasoningFactory();
        SeasoningFactory factoryB = new GuanXianSeasoningFactory();

        if (type.equals("舊成都披薩")){
            pizza = new ChengduPizza(factoryA);;
        }else if (type.equals("新北京披薩")){
            pizza = new PekingPizza(factoryB);
        }else {
            pizza = new ChengduPizza(factoryB);
        }
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }

    public static void main(String[] args) {
        PizzaStore pizzaStore = new StarBoundPizzaStore();
        Pizza pizza = pizzaStore.createPizza("舊成都披薩");
        System.out.println(pizza.toString());
        Pizza pizza1 = pizzaStore.createPizza("新北京披薩");
        System.out.println(pizza1.toString());
    }
}
結果:
開始準備 舊成都披薩:
以下原料準備完畢:虎皮青椒,郫縣豆瓣,花椒油,食用鹽
烘烤...
切片...
包裝...
披薩製作完畢:舊成都披薩
生產原料:虎皮青椒   郫縣豆瓣    花椒油 食用鹽
開始準備 新北京披薩:
以下原料準備完畢:虎皮青椒,老乾媽豆瓣醬,花椒油,食用鹽
烘烤...
切片...
包裝...
披薩製作完畢:新北京披薩
生產原料:虎皮青椒   老乾媽豆瓣醬  花椒油 食用鹽

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

-Advertisement-
Play Games
更多相關文章
  • 一、簡介: GridView是一個以表格形式顯示多張圖片等組件。它是按照行列的方式來顯示內容的,比如實現九宮格圖,用GridView是首選。 <! more 二、代碼塊: 看過我上一篇博客的同學應該知道,一步一步全部步驟寫出來是很費時間的,大概流程就那樣,所以這次網格視圖就直接上代碼塊了,步驟差不多 ...
  • 緣起 前端開發離不開Chrome的開發者工具,尤其是調試Android WebView時。然而,如果使用chrome://Inspect的方法,國內的開發者會驚奇地發現“空白啊”!為此,我發佈過這個離線包的解決方案!已經可以無需Fan牆就能調試了。但是,在使用過程中發現了以下問題: 解決 基於以上問 ...
  • nonatomic : 非原子屬性 atomic : 原子屬性 如果不寫關鍵字 那麼預設就是 原子屬性 - 多線程寫入屬性時,保證同一時間只有一個線程能夠執行寫入操作 - 單(線程)寫多(線程)讀線程技術,同樣有可能出現"臟數據",重新讀一下 - 性能較慢 線程安全 在多個線程進行讀寫操作的時候,仍 ...
  • 單例模式是設計模式中比較常見簡單的一種,典型雙重檢測寫法如下: 接下來對該寫法進行分析,為何這樣寫? 一、為何要同步: 多線程情況下,若是A線程調用getInstance,發現instance為null,那麼它會開始創建實例,如果此時CPU發生時間片切換,線程B開始執行,調用getInstance, ...
  • 數據持久化,也就是把數據保存到磁碟,以後可以再讀取出來使用(也可以再次更改或刪除)。很多場景需要數據持久化,比如為了減輕伺服器的訪問與存儲壓力,客戶端需要在本地做一些數據持久化的工作。iOS的數據持久化,有幾種方式,包括:自定義格式的文件、plistCoreData、FMDB等等。這裡記錄基於Cor... ...
  • 前言 在 "上一篇" 中我們學習了行為型模式的備忘錄模式(Memento Pattern)和狀態模式(Memento Pattern)。本篇則來學習下行為型模式的最後兩個模式,觀察者模式(Observer Pattern)和空對象模式模式(NullObject Pattern)。 觀察者模式 簡介 ...
  • "迭代器模式·原文地址" "更多《設計模式系列教程》" "更多免費教程" 博主按:《每天一個設計模式》旨在初步領會設計模式的精髓,目前採用 ( 靠這吃飯 )和 ( 純粹喜歡 )兩種語言實現。誠然,每種設計模式都有多種實現方式,但此小冊只記錄最直截了當的實現方式 :) 0. 項目地址 迭代器模式源碼: ...
  • 單例模式 介紹 模式:創建型 意圖:保證一個類只有一個實例,並提供一個訪問它的全局訪問點 解決:一個全局使用的類頻繁地創建與銷毀 場景: 唯一序列號 web中的計數器 I/O與資料庫的連接 …… 唯一序列號 web中的計數器 I/O與資料庫的連接 …… 實現方式 餓漢式 :靜態載入,線程安全 餓漢式 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...