1. 程式人生 > 實用技巧 >springboot,springSecurity中POST請求404

springboot,springSecurity中POST請求404

目錄

裝飾者模式

案例

喝奶茶是平時很常見的一件事情,奶茶中有牛奶,珍珠和椰果等等很多的材料,我們可以根據自己的喜愛來進行選擇。我們給店家說過我們想要的奶茶材料後,店家根據我們選擇的奶茶材料除了需要進行調製以外,最重要的就是根據材料進行算賬了,下面我們就來模擬這一過程。

1.各種材料:水

/**
 * 原料水
 */
public class Water {
    private int money = 1;
    public Water() {
        System.out.println("加水,價格:" + money + "元");
    }

    public int getMoney() {
        return this.money;
    }
}

牛奶:

/**
 * 材料牛奶
 */
public class Milk {
    private int money = 2;
    public Milk() {
        System.out.println("加牛奶,價值格:" + money + "元");
    }

    public int getMoney() {
        return money;
    }
}

珍珠:

/**
 * 材料珍珠
 */
public class Pearl {
    private int money = 3;
    public Pearl() {
        System.out.println("加珍珠,價值格:" + money + "元");
    }

    public int getMoney() {
        return money;
    }
}

椰果:

/**
 * 材料椰果
 */
public class Coconut {
    private int money = 3;
    public Coconut() {
        System.out.println("加椰果,價值格:" + money + "元");
    }

    public int getMoney() {
        return money;
    }
}

2.測試:

/**
 * 模擬買一杯珍珠椰果奶茶
 */
public class Main {
    public static void main(String[] args) {
        // 奶茶的預設必備材料:水
        Water water = new Water();
        // 加入牛奶
        Milk milk = new Milk();
        // 加入珍珠
        Pearl pearl = new Pearl();
        // 加入椰果
        Coconut coconut = new Coconut();
        // 計算價格
        int total = water.getMoney() + milk.getMoney() + pearl.getMoney() + coconut.getMoney();
        System.out.println("總價格:" + total + "元");
    }
}

3.模擬結果:

加一份水,價格:1元
加一份牛奶,價值格:2元
加一份珍珠,價值格:3元
加一份椰果,價值格:3元
總價格:9元

我們通過例項化我們選擇的材料進行調製奶茶,最後通過相加各種材料的價格得到最終需要進行付款的價格。這裡雖然沒有什麼問題,但是如果我們想要兩份珍珠,那麼除了要在例項化一個材料以外,還需要重新計算價格。這個計算價格的方式顯得不夠靈活,我們希望在選擇了所有的材料後,就能立馬得出總價格是多少。下面就通過裝飾者模式來對這一問題進行改善。

模式介紹

裝飾模式(結構型模式)指的是在不必改變原類檔案使用繼承的情況下,動態地擴充套件一個物件的功能。它是通過建立一個包裝物件,也就是裝飾來包裹真實的物件。

可以看到裝飾者模式的介紹非常言簡意賅,其中的重點就是建立包裝物件來動態的擴充套件功能。

角色構成:

  • Component(抽象構件):它是具體構件和抽象裝飾類的共同父類,聲明瞭在具體構件中實現的業務方法,它的引入可以使客戶端以一致的方式處理未被裝飾的物件以及裝飾之後的物件,實現客戶端的透明操作。
  • ConcreteComponent(具體構件):它是抽象構件類的子類,用於定義具體的構件物件,實現了在抽象構件中宣告的方法,裝飾器可以給它增加額外的職責(方法)。
  • Decorator(抽象裝飾類):它也是抽象構件類的子類,用於給具體構件增加職責,但是具體職責在其子類中實現。它維護一個指向抽象構件物件的引用,通過該引用可以呼叫裝飾之前構件物件的方法,並通過其子類擴充套件該方法,以達到裝飾的目的。
  • ConcreteDecorator(具體裝飾類):它是抽象裝飾類的子類,負責向構件新增新的職責。每一個具體裝飾類都定義了一些新的行為,它可以呼叫在抽象裝飾類中定義的方法,並可以增加新的方法用以擴充物件的行為。

這裡的具體構件Decorator和抽象裝飾類ConcreteDecorator都實現了相同的抽象構件,因此客戶端在使用時並不會感覺到物件在裝飾前和裝時候有什麼不同。

UML類圖:

在抽象裝飾類Decorator中維護了一個對Component抽象構件物件的引用,並可以通過構造方法或Setter方法將一個Component型別的物件注入進來。同時由於Decorator類實現了抽象構件Component介面,因此需要實現方法method(),但是在Decorator中只是呼叫原有component物件的method()方法,它沒有真正實施裝飾,而是提供一個統一的介面,將具體裝飾過程交給子類完成。

程式碼改造

通過上面分析過的這幾個角色間的關係,下面就對上面的程式碼進行改造。

1.首先定義抽象構建類:

/**
 * 抽象構件
 */
public interface Material {
    // 定義計算金額的方法
    int sum();
}

2.具體構件類:

/**
 * 具體構件類角色
 */
public class Water implements Material {
    private int money = 1;

    public Water() {
        System.out.println("加一份水,價格:" + money + "元");
    }

    // 這裡實現介面中的方法,返回自身的價格
    public int sum() {
        return this.getMoney();
    }

    public int getMoney() {
        return money;
    }
}

3.抽象裝飾類:

/**
 * 抽象裝飾類:配料類
 */
public abstract class Batching implements Material {
    protected Material material;

    public Batching(Material material) {
        this.material = material;
    }

    // 實現介面中的方法,實際呼叫的是具體構件類中的方法
    public int sum() {
        return material.sum();
    }
}

4.三個裝飾類:

牛奶類:

/**
 * 具體裝飾類
 */
public class Milk extends Batching {
    private int money = 2;

    public Milk(Material material) {
        super(material);
        System.out.println("加一份牛奶,價值格:" + money + "元");
    }

    // 重寫抽象類中的方法,主要是做一些額外的操作,在這裡是用於返回自身的價格
    public int sum() {
        return super.sum() + this.getMoney();
    }

    public int getMoney() {
        return money;
    }
}

珍珠類:

/**
 * 具體裝飾類
 */
public class Pearl extends Batching {
    private int money = 3;

    public Pearl(Material material) {
        super(material);
        System.out.println("加一份珍珠,價值格:" + money + "元");
    }

    // 重寫抽象類中的方法,主要是做一些額外的操作,在這裡是用於返回自身的價格
    public int sum() {
        return super.sum() + this.getMoney();
    }

    public int getMoney() {
        return money;
    }
}

椰果類:

/**
 * 具體裝飾類
 */
public class Coconut extends Batching {
    private int money = 3;

    public Coconut(Material material) {
        super(material);
        System.out.println("加一份椰果,價值格:" + money + "元");
    }

    // 重寫抽象類中的方法,主要是做一些額外的操作,在這裡是用於返回自身的價格
    public int sum() {
        return super.sum() + this.getMoney();
    }

    public int getMoney() {
        return money;
    }
}

5.測試類:

/**
 * 模擬點一杯珍珠椰果奶茶
 */
public class Main {
    public static void main(String[] args) {
        Coconut coconut = new Coconut(new Pearl(new Milk(new Water())));
        System.out.println("總價格:" + coconut.sum());
    }
}

6.測試結果:

加一份水,價格:1元
加一份牛奶,價值格:2元
加一份珍珠,價值格:3元
加一份椰果,價值格:3元
總價格:9

這裡首先是建立了一個抽象的構件類Component,再把Water類當作了一個具體的構件類。同時引入抽象裝飾類Batching,在類中維護了一個Component類的例項,使得在實現介面方法時可以呼叫具體構件類的方法。再把MilkPearlCoconut三個類當作具體的裝飾類,重寫抽象裝飾類Batching類中的方法,同時可以進行擴充套件,在這裡僅僅是返回了各配料類的自身價格,還可以對配料類做打折操作,例如在實現方法sum()中這樣寫:return super.sum() + this.getMoney()*0.5;,相當於配料打五折。

而且這裡再新增其他配料時也和簡單,只用在外層再包裹一層配料類的例項就可以了,例如這裡新增兩份椰果:

public static void main(String[] args) {
    Coconut coconut = new Coconut(new Coconut(new Pearl(new Milk(new Water()))));
    System.out.println("總價格:" + coconut.sum());
}

模式應用

說到裝飾著模式其實並不陌生,因為大家都應該聽過 Java 中的 IO 就是運用了裝飾者模式的思想,下面看一個 IO 讀取檔案示例:

1.測試類:

public class Main {
    public static void main(String[] args) throws IOException {
        // 1. 從當前資料夾中讀取檔案為輸入流
        InputStream fileInputStream = new FileInputStream("decorator/text.txt");
        // 2 從位元組流中讀取內容
        // 2.1 定義一個緩衝位元組陣列
        byte[] buffer = new byte[1024];
        // 2.2 定義讀取標誌位
        int read;
        // 2.3 迴圈讀取位元組到緩衝陣列中
        while ((read = fileInputStream.read(buffer)) != -1) {
            // 將讀取的位元組轉換為字串物件
            String str = new String(buffer, 0, read);
            System.out.println(str);
        }
        // 2.4 檔案位元組流沒有 mark/reset 功能
        System.out.println("markSupported:" + fileInputStream.markSupported());
        System.out.println("---------------------");

        // 3. 將位元組流包裝為緩衝位元組流
        InputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("decorator/text.txt"));
        // 3.1 緩衝位元組流支援 mark、reset 的功能
        System.out.println("markSupported:" + bufferedInputStream.markSupported());
        // 3.2 讀取第一個位元組的資料
        System.out.println((char) bufferedInputStream.read());
        // 3.3 標記當前輸入流中的位置
        bufferedInputStream.mark(0);
        // 3.4 讀取下三個位元組資料
        System.out.println((char) bufferedInputStream.read());
        System.out.println((char) bufferedInputStream.read());
        // 3.5 重置到 2.2.4 中標記的位置
        bufferedInputStream.reset();
        // 3.6 讀取下一個位元組資料
        System.out.println((char) bufferedInputStream.read());
    }
}

2.測試檔案位置如圖所示:

​ 3.測試結果:

開始輸出檔案內容
hello
world
輸出檔案結束
markSupported:false
---------------------
markSupported:true
h
e
l
e

從程式碼中可以看到InputStream是一個介面,FileInputStream是其中一個實現子類,它作為具體構件實現了InputStream中讀取資料的方法。而BufferedInputStream擴充套件了標記/重置功能(如果對這一功能感興趣,可以參考BufferedInputStream中的mark()和reset()用法,及其中readlimit相關的問題),它作為具體裝飾類的角色。而從繼承關係中我們可以找到它的父類FilterInputStream,就是一個抽象裝飾著角色。下面是這幾個類之間的UML類圖:

總結

1.主要優點

  • 對於擴充套件一個物件的功能,裝飾模式比繼承更加靈活性,不會導致類的個數急劇增加。
  • 可以通過一種動態的方式來擴充套件一個物件的功能,通過配置檔案可以在執行時選擇不同的具體裝飾類,從而實現不同的行為。
  • 可以對一個物件進行多次裝飾,通過使用不同的具體裝飾類以及這些裝飾類的排列組合,可以創造出很多不同行為的組合,得到功能更為強大的物件。
  • 具體構件類與具體裝飾類可以獨立變化,使用者可以根據需要增加新的具體構件類和具體裝飾類,原有類庫程式碼無須改變,符合“開閉原則”。

2.主要缺點

  • 使用裝飾模式進行系統設計時將產生很多小物件,這些物件的區別在於它們之間相互連線的方式有所不同,而不是它們的類或者屬性值有所不同,大量小物件的產生勢必會佔用更多的系統資源,在一定程式上影響程式的效能。
  • 裝飾模式提供了一種比繼承更加靈活機動的解決方案,但同時也意味著比繼承更加易於出錯,排錯也很困難,對於多次裝飾的物件,除錯時尋找錯誤可能需要逐級排查,較為繁瑣。

3.適用場景

  • 在不影響其他物件的情況下,以動態、透明的方式給單個物件新增職責。
  • 當不能採用繼承的方式對系統進行擴充套件或者採用繼承不利於系統擴充套件和維護時可以使用裝飾模式。不能採用繼承的情況主要有兩類:第一類是系統中存在大量獨立的擴充套件,為支援每一種擴充套件或者擴充套件之間的組合將產生大量的子類,使得子類數目呈爆炸性增長;第二類是因為類已定義為不能被繼承(如Java語言中的final類)。

參考資料

本篇文章github程式碼地址:https://github.com/Phoegel/design-pattern/tree/main/decorator
轉載請說明出處,本篇部落格地址:https://www.cnblogs.com/phoegel/p/13966052.html