quarkus依賴註入之八:裝飾器(Decorator)

来源:https://www.cnblogs.com/bolingcavalry/archive/2023/08/07/17608079.html
-Advertisement-
Play Games

### 歡迎訪問我的GitHub > 這裡分類和彙總了欣宸的全部原創(含配套源碼):[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) ### 本篇概覽 - 本篇是《quarkus依賴註入》系列的第 ...


歡迎訪問我的GitHub

這裡分類和彙總了欣宸的全部原創(含配套源碼):https://github.com/zq2599/blog_demos

本篇概覽

  • 本篇是《quarkus依賴註入》系列的第八篇,目標是掌握quarkus實現的一個CDI特性:裝飾器(Decorator)
  • 提到裝飾器,熟悉設計模式的讀者應該會想到裝飾器模式,個人覺得下麵這幅圖很好的解釋了裝飾器模式,左下角的紅框是關鍵點:自己的send方法中,先調用父類的send(也就是被裝飾類的send),然後才是自己的業務邏輯
54
  • quarkus也支持裝飾器模式,通過註解DecoratorDelegate實現,今天咱們就通過實戰掌握如何在quarks框架下通過裝飾器擴展應用
  • quarkus是按照CDI的標準來支持裝飾器模式的,下圖來自官方文檔

image-20220409210857612

  • 接下來進入實戰環節

實戰功能說明

  • 網上講述裝飾器模式的文章中,有個咖啡價格的例子非常經典,如下圖所示:
  1. 一杯意式濃縮咖啡(Espresso)價格3美元
  2. 拿鐵(Latte)由意式濃縮+牛奶組成,價格是意式濃縮和牛奶之和,即5美元
  3. 焦糖瑪奇朵(CaramelMacchiato)由拿鐵+焦糖組成,價格比拿鐵多了焦糖的1美元,即6美元
  4. 每種咖啡都是一種對象,價格由getPrice方法返回
流程圖 - 2022-04-09T165959.292
  • 在上述場景中,當咖啡的內容不斷豐富,咖啡價格也要做相應調整,裝飾器的作用是讓代碼優雅的應對變化,對內代碼整潔低耦合,對外保持統一介面getPrice

  • 裝飾器模式本身並不是本篇的重點,咱們還是聚焦quarkus下的裝飾器功能:在咖啡價格的基礎上,通過裝飾器計算出拿鐵的價格

  • 接下來開始編碼

編碼實戰

  • 首先定義介面Coffee.java,不論是意式濃縮、拿鐵、還是其他種類,對外都稱之為Coffee,都有getPrice方法
package com.bolingcavalry.decorator;

public interface Coffee {

    /**
     * 咖啡名稱
     * @return
     */
    String name();

    /**
     * 當前咖啡的價格
     * @return
     */
    int getPrice();
}
  • 然後是最基礎的意式濃縮咖啡,非常簡單的一個bean,定價3美元,這裡有個細節要註意:name方法中寫死了字元串Espresso,而沒用getClass().getSimpleName(),這是因為在quarkus容器中,Espresso的bean並非Espresso類型,而是動態生成的代理類,所以getClass返回的類不是Espresso
package com.bolingcavalry.decorator.impl;

import com.bolingcavalry.decorator.Coffee;

import javax.enterprise.context.ApplicationScoped;

/**
 * 意式濃縮咖啡,價格3美元
 */
@ApplicationScoped
public class Espresso implements Coffee {

    @Override
    public String name() {
        return "Espresso";
    }

    @Override
    public int getPrice() {
        return 3;
    }
}
  • 接下來就是重點了,拿鐵,由意式濃縮+牛奶組成,代碼如下,有幾處要註意的地方稍後會提到
package com.bolingcavalry.decorator.impl;

import com.bolingcavalry.decorator.Coffee;
import io.quarkus.arc.Priority;
import io.quarkus.logging.Log;

import javax.decorator.Decorator;
import javax.decorator.Delegate;
import javax.inject.Inject;

@Decorator
@Priority(11)
public class Latte implements Coffee {
    /**
     * 牛奶價格:2美元
     */
    private static final int MILK_PRICE = 2;

    @Delegate
    @Inject
    Coffee delegate;

    @Override
    public String name() {
        return "Latte";
    }

    @Override
    public int getPrice() {
        // 將Latte的代理類列印出來,看quarkus註入的是否正確
        Log.info("Latte's delegate type : " + this.delegate.name());
        return delegate.getPrice() + MILK_PRICE;
    }
}
  • 上述代碼有以下幾處要註意
  1. 先明確目的:我們設計Latte這個bean,本意是通過裝飾器模式來裝飾Espresso,因此才會用到quarkus的裝飾器功能
  2. 使用quarkus的裝飾器功能時,有兩件事必須要做:裝飾類要用註解Decorator修飾,被裝飾類要用註解Delegate修飾
  3. 因此,Latte被註解Decorator修飾,Latte的成員變數delegate是被裝飾類,要用註解Delegate修飾,
  4. Latte的成員變數delegate並未指明是Espresso,quarkus會選擇Espresso的bean註入到這裡
  5. 在getPrice方法中列印出delegate.name方法的返回值,驗證delegate的身份,以確認quarkus註入的是否正確
  6. 註解Priority很重要,留在接下來的CaramelMacchiato類(焦糖瑪奇朵)寫完後再說清楚
  • 接下來是CaramelMacchiato類(焦糖瑪奇朵),有幾處要註意的地方稍後會說明
package com.bolingcavalry.decorator.impl;

import com.bolingcavalry.decorator.Coffee;
import io.quarkus.arc.Priority;
import io.quarkus.logging.Log;

import javax.decorator.Decorator;
import javax.decorator.Delegate;
import javax.inject.Inject;

/**
 * 焦糖瑪奇朵:拿鐵+焦糖
 */
@Decorator
@Priority(10)
public class CaramelMacchiato implements Coffee {

    /**
     * 焦糖價格:1美元
     */
    private static final int CARAMEL_PRICE = 1;

    @Delegate
    @Inject
    Coffee delegate;

    @Override
    public String name() {
        return "CaramelMacchiato";
    }

    @Override
    public int getPrice() {
        // 將CaramelMacchiato的代理類列印出來,看quarkus註入的是否正確
        Log.infov("CaramelMacchiato's delegate type : " + this.delegate.name());
        return delegate.getPrice() + CARAMEL_PRICE;
    }
}
  • CaramelMacchiato代碼的邏輯和Latte的差不多,都用了註解Decorator和Delegate,目的是為了做Latte的裝飾器
  • 重點關註的是成員變數delegate,其類型、名稱、註解,都和Latte的delegate一模一樣:
@Delegate
@Inject
Coffee delegate;

重要知識點

  • 看到這裡,相信您也發現了問題所在:CaramelMacchiato和Latte都有成員變數delegate,其註解和類型聲明都一模一樣,那麼,如何才能保證Latte的delegate註入的是Espresso,而CaramelMacchiato的delegate註入的是Latte呢?
  • 此刻就是註解Priority在發揮作用了,CaramelMacchiato和Latte都有註解Priority修飾,屬性值卻不同,屬性值越大越接近原始類Espresso,如下圖,所以,Latte裝飾的就是Espresso,CaramelMacchiato裝飾的是Latte
流程圖 - 2022-04-09T203421.135

單元測試類

  • 最後是單元測試類,成員變數的類型是Coffee,也就是說quarkus容器會自動註入裝飾過的CaramelMacchiato類型的bean,而testDecoratorPrice方法中斷言coffee.getPrice()的值等於6,如果註入caffee的bean不是CaramelMacchiato類型,斷言就會失敗
package com.bolingcavalry;

import com.bolingcavalry.decorator.Coffee;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import javax.inject.Inject;

@QuarkusTest
public class DecoratorTest {

    @Inject
    Coffee coffee;

    @Test
    public void testDecoratorPrice() {
        Assertions.assertEquals(6, coffee.getPrice());
    }
}

驗證

  • 執行單元測試,如下圖,單元測試通過表示coffee註入的是CaramelMacchiato類型的bean,再看右側的日誌,CaramelMacchiato的成員變數delegate是Latte類型,Latte的成員變數delegate是Espresso類型,都按照咱們的預期準確註入了
    67

  • 緊接著再做個嘗試:將Latte的註解Priority的屬性值改小,小於CaramelMacchiato的10,如下圖紅框,如此一來,CaramelMacchiato的優先順序更大,因此更靠近Espresso,由它去裝飾Espresso,Latte離Espresso更遠,所以它裝飾的是CaramelMacchiato

image-20220409205649353
  • 再次運行單元測試,如下圖,首先測試依舊能通過,這個好理解,無論裝飾邏輯怎麼變,最終的bean的getPrice返回值,都是意式濃縮+牛奶+焦糖的價格之和,然後在看右側日誌信息,果然,CaramelMacchiato註入的成員變數是Espresso,Latte註入的成員變數是CaramelMacchiato

68

  • 至此,裝飾器的編碼實戰已完成,相信您可以在應用中用熟練使用裝飾器來擴展bean能力,並且保持與原有bean之間的代碼低耦合

與攔截器的不同

  • 如果您看過《攔截器》一文,應該會發現,同樣的功能用攔截器也能實現,那為何還要多出個裝飾器呢?
  • 其實網上也有類似的討論,首先是Stack Overflow上分析,一個高贊的觀點是:通常情況下,一個裝飾器被用於一個特定類上,而攔截器用於攔截多個類
  • 這篇2012年的關於CDI的文章《Interceptors and Decorators tutorial》中的對比更好理解:

55

  • 個人理解:
  1. 攔截器適合做一些通用的事情,例如日誌、異常處理等,可以為多個bean服務
  2. 裝飾器適合做特定的事情,例如本篇的演示代碼中,計算價格是被裝飾類的特性,其他bean沒有這個功能,所以裝飾器也只能用在,作為核心功能的增強或者完善

歡迎關註博客園:程式員欣宸

學習路上,你不孤單,欣宸原創一路相伴...


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

-Advertisement-
Play Games
更多相關文章
  • # 1 HTML5新特性 ## 1.1 概述 HTML5 的新增特性主要是針對於以前的不足,增加了一些新的標簽、新的表單和新的表單屬性等。 這些新特性都有相容性問題,基本是 **IE9+ 以上版本的瀏覽器**才支持,如果不考慮相容性問題,可以大量使用這些新特性。 ## 1.2 語義化標簽 (★★) ...
  • 系統要求 C/S架構的單體桌面應用,可以滿足客戶個性化需求,易於升級和維護。相比於一代Winform,界面要求美觀,控制項豐富可定製。 解決方案 依托.Net6開發平臺,採用模塊化思想設計(即分而治之的策略),每個模塊採用DDD分層設計。前端選用WPF + Prism框架,後端選用ABP + EF框架 ...
  • ### 歡迎訪問我的GitHub > 這裡分類和彙總了欣宸的全部原創(含配套源碼):[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) ### 本篇概覽 - 本篇是《quarkus依賴註入》的第九篇 ...
  • import os import pandas as pd def md5(string:str=''): import hashlib md5 = hashlib.md5() md5.update(string.encode('utf-8')) return md5.hexdigest() # I ...
  • 註釋是在執行時被忽略的文本。 註釋可用於解釋代碼,使其更易讀。 註釋還可用於在測試替代代碼時防止代碼執行。 Go支持單行或多行註釋。 ## Go單行註釋 單行註釋以兩個正斜杠(`//`)開頭。 在`//`和行尾之間的任何文本都將被編譯器忽略(不會被執行)。 示例 ```Go // This is a ...
  • ### 寫在前面 > 動態 `SQL` 語句是部分或者整個 `SQL` 語句在運行時才能確定,可以更好的與用戶進行交互,大大提高了`SQL`的靈活性 ### 一、執行SQL語句 #### 1.1 執行無入參SQL ① 語法 ```sql EXECUTE IMMEDIATE SQLStatement ...
  • 本文介紹了 Go 語言中的基本概念,包括變數、數據類型、數據類型轉換以及進位轉換。我們將詳細探討整數、浮點數、字元、布爾值和字元串等數據類型,以及如何在實際編程中高效地使用它們。 ...
  • ## 前言 假設一個場景,服務端部署在內網,客戶端需要通過暴露在公網的nginx與服務端進行通信。為了避免在公網進行 http 明文通信造成的信息泄露,nginx與客戶端之間的通信應當使用 https 協議,並且nginx也要驗證客戶端的身份,也就是mTLS雙向加密認證通信。 這條通信鏈路有三個角色 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...