裝飾者模式,從吃黃燜雞開始說起

来源:https://www.cnblogs.com/jamaler/archive/2019/09/23/11571592.html
-Advertisement-
Play Games

黃燜雞米飯最熱賣的外賣之一,國人都喜歡吃,吃過黃燜雞米飯的應該都知道,除了黃燜雞米飯主體外,還可以添加各種配菜,如土豆、香菇、鵪鶉蛋、青菜等。如果需要你來設計一套黃燜雞米飯結賬系統,你該如何設計呢? 前置條件:主體:黃燜雞米飯 價格:16,配菜:土豆 價格:2、香菇 價格:2、鵪鶉蛋 價格:2、青菜 ...


黃燜雞米飯最熱賣的外賣之一,國人都喜歡吃,吃過黃燜雞米飯的應該都知道,除了黃燜雞米飯主體外,還可以添加各種配菜,如土豆、香菇、鵪鶉蛋、青菜等。如果需要你來設計一套黃燜雞米飯結賬系統,你該如何設計呢?

前置條件:主體:黃燜雞米飯 價格:16,配菜:土豆 價格:2、香菇 價格:2、鵪鶉蛋 價格:2、青菜 價格:1.5

這還不簡單?看我的,你隨手就來了下麵這段代碼。

public class HuangMenJiMiFan {
    // 黃燜雞價格
    private double huangMenJiPrice = 16D;
    // 土豆價格
    private double potatoPrice = 2D;
    // 鵪鶉蛋價格
    private double eggPrice = 2D;
    // 香菇價格
    private double mushroomPrice = 2D;
    // 青菜價格
    private double vegPrice = 1.5D;
    // 總價格
    private double totalPrice = 0D;
    // 訂單描述
    private StringBuilder desc = new StringBuilder("黃燜雞米飯 ");

    // 是否加土豆
    private boolean hasPotato = false;
    // 是否加鵪鶉蛋
    private boolean hasEgg = false;
    // 是否加香菇
    private boolean hasMushroom = false;
    // 是否加蔬菜
    private boolean hasVeg = false;

    public HuangMenJiMiFan(){
        this.totalPrice = this.huangMenJiPrice;
    }

    public void setHasPotato(boolean hasPotato) {
        this.hasPotato = hasPotato;
    }

    public void setHasEgg(boolean hasEgg) {
        this.hasEgg = hasEgg;
    }

    public void setHasMushroom(boolean hasMushroom) {
        this.hasMushroom = hasMushroom;
    }

    public void setHasVeg(boolean hasVeg) {
        this.hasVeg = hasVeg;
    }

    public String getDesc(){
        if (hasEgg){
            this.desc.append("+ 一份鵪鶉蛋 ");
        }
        if (hasMushroom){
            this.desc.append("+ 一份香菇 ");
        }
        if (hasPotato){
            this.desc.append("+ 一份土豆 ");
        }
        if (hasVeg){
            this.desc.append("+ 一份蔬菜 ");
        }
        return desc.toString();
    }

    public double cost(){
        if (hasEgg){
            this.totalPrice +=this.eggPrice;
        }
        if (hasMushroom){
            this.totalPrice +=this.mushroomPrice;
        }
        if (hasPotato){
            this.totalPrice +=this.potatoPrice;
        }
        if (hasVeg){
            this.totalPrice +=this.vegPrice;
        }
        return totalPrice;
    }
}

只要在點黃燜雞米飯的時候,把添加的配菜設置成true就好,這段代碼確實解決了黃燜雞米飯結算問題。但是我需要加兩份土豆呢?我需要添加一種新配菜呢?或者我新增一個黃燜排骨呢?這時候實現起來就需要去改動原來的代碼,這違背了設計模式的開放-關閉原則

開放-關閉原則:類應該對擴展開放,對修改關閉

上面的設計違背了開放-關閉原則,為了避免這個問題,採用裝飾者模式似乎是一種可行的解決辦法。

裝飾者模式:動態的給一個對象添加一些額外的職責,就增加功能來說,裝飾模式比生成子類更為靈活。

裝飾者模式的通用類圖如下:
裝飾者模式的通用類圖
從類圖中,我們可以看出裝飾者模式有四種角色:

  • Component:核心抽象類,裝飾者和被裝飾者都需要繼承這個抽象類
  • ConcreteComponent:對裝飾的對象,該類必須繼承Component
  • Decorator:裝飾者抽象類,抽象出具體裝飾者需要裝飾的介面
  • ConcreteDecorator:具體的裝飾者,該類必須繼承Decorator類,並且裡面有一個變數指向Component抽象類

裝飾者模式的核心概念我們都知道了,那就來實現一把,用裝飾者模式來設計黃燜雞米飯的結賬系統。

Component類的設計,仔細想想,不管黃燜雞米飯還是配菜都會涉及到金額計算。所以我們把該方法抽象到Component類。來設計我們黃燜雞米飯結賬系統的Component類,我們取名叫做Food,Food類的具體設計如下:

/**
 * 核心抽象類
 */
public abstract class Food {

    String desc = "食物描述";

    public String getDesc() {
        return this.desc;
    }
    // 價格計算
    public abstract double cost();
}

ConcreteComponent類是我們具體的被裝飾對象,我們這裡的裝飾對象是黃燜雞米飯,我們來設計我們黃燜雞米飯的被裝飾對象Rice類,Rice類的具體實現如下:

/**
 * 被裝飾者-黃燜雞米飯
 */
public class Rice extends Food{
    public Rice(){
        this.desc ="黃燜雞米飯";
    }
    @Override
    public double cost() {
        // 黃燜雞米飯的價格
        return 16D;
    }
}

Decorator類是裝飾者的抽象類,我們需要定義一個getDesc()的抽象介面,因為在Food類中,getDesc()不是抽象的,在後面的具體裝飾者中,需要重寫getDesc()類,所以我們需要將抽象在裝飾者這一層。我們來設計黃燜雞米飯結賬系統的裝飾者抽象類FoodDecoratorFoodDecorator類的具體設計如下:

public abstract class FoodDecorator extends Food {
    // 獲取描述
    public abstract String getDesc();
}

ConcreteDecorator類是具體的裝飾者,我們有四個具體的裝飾者,分別是土豆、香菇、鵪鶉蛋、青菜,具體的裝飾者需要做的事情是計算出被裝飾者裝飾完裝飾品後的總價格和更新商品的描述。四個具體裝飾者的設計如下:

public class Egg extends FoodDecorator {
    String desc = "雞蛋";
    // 存放Component對象,該對象可能是被裝飾後的
    Food food;

    public Egg(Food food){
        this.food = food;
    }

    // 計算總價 當前Component對象的價格加上當前裝飾者的價格
    @Override
    public double cost() {
        return food.cost() + 2D;
    }
    @Override
    public String getDesc() {
        return food.getDesc()+" + "+this.desc;
    }
}
public class Mushroom extends FoodDecorator {
    String desc = "香菇";
    Food food;

    public Mushroom(Food food){
        this.food = food;
    }
    // 計算總價
    @Override
    public double cost() {
        return food.cost() + 2D;
    }
    @Override
    public String getDesc() {
        return food.getDesc()+" + "+this.desc;
    }
}
public class Potato extends FoodDecorator {
    String desc = "土豆";
    Food food;

    public Potato(Food food){
        this.food = food;
    }
    // 計算總價
    @Override
    public double cost() {
        return food.cost() + 2D;
    }
    @Override
    public String getDesc() {
        return food.getDesc()+" + "+this.desc;
    }
}
public class Veg extends FoodDecorator {
    String desc = "蔬菜";
    Food food;
    public Veg(Food food){
        this.food = food;
    }
    // 計算總價
    @Override
    public double cost() {
        return food.cost() + 1.5D;
    }
    @Override
    public String getDesc() {
        return food.getDesc()+" + "+this.desc;
    }
}

裝飾者的所有角色都實現完了,我們來測試一下使用裝飾者模式之後的黃燜雞結賬系統,編寫一個App測試類。

public class App {
    public static void main(String[] args) {
        // 點一份米飯
        Rice rice = new Rice();
        // 加個雞蛋
        Egg egg = new Egg(rice);
        // 在加土豆
        Potato potato = new Potato(egg);
        // 再加一份白菜
        Veg veg = new Veg(potato);
        System.out.println(veg.getDesc());
        System.out.println(veg.cost());
    }
}

測試結果

我們的描述和金額都是正確的,可能你還是沒怎麼明白裝飾者模式,一起來看看我們的黃燜雞米飯被裝飾後的示意圖:


我們的黃燜雞米飯共有三層裝飾,第一層是雞蛋,第二層是土豆,第三層是蔬菜。我們在最後調用價格計算和商品描述都是調用了最外層的裝飾者的方法,有點像遞歸一樣,每一層的裝飾者都有被前一個裝飾者裝飾後的黃燜雞米飯對象。裡面會產生想遞歸一樣的調用。希望看完這張圖之後,對你理解裝飾者模式有幫助。

使用裝飾者模式之後的黃燜雞米飯結賬系統,在新增配菜或者產品時,我們不需要修改原先的功能,只需要對類進行擴展就好了,這完全遵循了開放-關閉原則

裝飾者模式的優點

  • 裝飾類和被裝飾類可以獨立發展,而不會互相耦合,換句話說,就是Component類無須知道Decorator類,Decorator類也不用知道具體的被裝飾者。
  • 裝飾者模式是繼承關係的一個替代方案,從上面的黃燜雞米飯的案例中,我們可以看出,不管裝飾多少層,返回的對象還是Component
  • 裝飾者模式可以動態的擴展一個實現類的功能

裝飾者模式的優點

  • 多層裝飾模式比較複雜,你可以想象一下剝洋蔥,如果最裡面的裝飾出了問題,你的工作量會有多大?

最後多說一句,JDK 中的 java.io 就是用裝飾者模式實現的,有興趣的可以去深入瞭解一下。

源代碼

文章不足之處,望大家多多指點,共同學習,共同進步

最後

打個小廣告,歡迎掃碼關註微信公眾號:「平頭哥的技術博文」,一起進步吧。
平頭哥的技術博文


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

-Advertisement-
Play Games
更多相關文章
  • 本文主要使用坐標軸的使用來繪製多邊形,點位則都是在y軸上尋找,這種方法能夠更好的理解圖形與修改。 這張圖片是代碼執行後的結果 ...
  • 1. 概述 Proxy 用於修改某些操作的預設行為,等同於在語言層面做出修改,所以屬於一種『元編程』即對編程語言進行編程。 1.1 理解 Proxy 是在目標對象之前架設一層『攔截』,外部對對象的訪問,都需要經過該層攔截。因此在攔截中對外界的訪問進行過濾和改寫。 在Es6 中 提供了原生的 Prox ...
  • 快來點我吧 ...
  • function getCount(arr, rank,ranktype){ var obj = {}, k, arr1 = []; for (var i = 0, len = arr.length; i < len; i++) { k = arr[i]; if (obj[k]) obj[k]++;... ...
  • 博客園美化博客隨筆目錄 基於 在`2014 5 11`寫的目錄代碼基礎上進行改進 一.js代碼 二.css代碼 三.展示效果 未打開狀態 展開效果 hover效果 三.在原先的基礎上解決的bug 1.目錄由於其他js導致沒法載入(最主要修改的內容) 2.修改了樣式 3.修改了hover樣式讓他看起來 ...
  • jQuery基礎(三)- 事件篇 1、jQuery滑鼠事件之click與dbclick事件 click方法用於監聽用戶單擊操作,dbclick方法用於監聽用戶雙擊操作,這兩個方法用法及其類似,所以這隻介紹click事件,只有單擊釋放後才生效,而且同一元素不能同時綁定click和dbclick事件 方 ...
  • 畢竟是聊聊曾經,放一張大學課堂上靈光一現,手寫的一個我曾經一直使用的網名 前言 原文地址: "Nealyang/personalBlog" 講真,的確是運氣,才有機會進大廠。也沒想到,那篇一年半工作經驗試水杭州大廠的面經如此受歡迎。後面也有很多朋友在群里問我,你是如何學習的? 此篇為xxx 經驗進阿 ...
  • 一 語法 模板渲染的 "官方文檔" 關於模板渲染你只需要記兩種特殊符號(語法): {{ }}和 {% %} 變數相關的用{{}},邏輯相關的用{%%}。 二 變數 在Django的模板語言中按此語法使用:{{ 變數名 }}。 當模版引擎遇到一個變數,它將計算這個變數,然後用結果替換掉它本身。 變數的 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...