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

来源: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
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...