09 結構型模式之裝飾者模式(decorator)

wyaoyao93發表於2020-12-13

1 概述

1.1 引入

我們先來看一個快餐店的例子。

快餐店有炒麵、炒飯這些快餐,可以額外附加雞蛋、火腿、培根這些配菜,當然加配菜需要額外加錢,每個配菜的價錢通常不太一樣,那麼計算總價就會顯得比較麻煩。

在這裡插入圖片描述
如果像上面的類圖一樣,通過繼承的方式實現,最大的弊端就是,會引起類爆炸,比如我們新增炒河粉,就要定義一個炒河粉類繼承FastFood,在定義一個雞蛋炒河粉,培根炒粉;同樣如果新增一種配料芝士,就需要在炒麵,炒飯分別提供芝士炒麵,芝士炒飯。如果是需要雞蛋培根炒麵呢,難道再定義一個雞蛋培根炒麵

使用繼承的方式存在的問題:

  • 擴充套件性不好

    如果要再加一種配料(火腿腸),我們就會發現需要給FriedRice和FriedNoodles分別定義一個子類。如果要新增一個快餐品類(炒河粉)的話,就需要定義更多的子類。

  • 產生過多的子類

定義:

​ 指在不改變現有物件結構的情況下,動態地給該物件增加一些職責(即增加其額外功能)的模式。

1.2 結構

裝飾(Decorator)模式中的角色:

  • 抽象構件(Component)角色 :定義一個抽象介面以規範準備接收附加責任的物件。比如這裡的炒飯,炒粉可以抽象出一個基類(上面類圖中的FastFood)
  • 具體構件(Concrete Component)角色 :實現抽象構件,通過裝飾角色為其新增一些職責。比如上面類圖中的炒飯,炒麵
  • 抽象裝飾(Decorator)角色 : 繼承或實現抽象構件,幷包含具體構件的例項,可以通過其子類擴充套件具體構件的功能。抽象出一個抽象的裝飾者,所有的裝飾者需要繼承該抽象裝飾者
  • 具體裝飾(ConcreteDecorator)角色 :實現抽象裝飾的相關方法,並給具體構件物件新增附加的責任。比如上面中的雞蛋,培根等可新增的配菜。

2 實現

使用裝飾者實現上面的案例

  1. 定義一個抽象構建角色,這裡就是炒飯,炒麵我們抽象出一個快餐基類
package study.wyy.design.decorator.after;

import lombok.Getter;
import lombok.Setter;

/**
 * @author by wyaoyao
 * @Description
 * @Date 2020/12/12 9:35 下午
 * 快餐基類
 */

public abstract class FastFood {
    @Getter@Setter
    private final String name;

    @Getter@Setter
    private final float price;

    public FastFood(String name, float price) {
        this.name = name;
        this.price = price;
    }

    /****
     * 計算價格,可以交給具體的子類實現
     * 預設就是返回屬性中的價格
     * @return
     */
    public  float cost(){
        return getPrice();
    }
}

  1. 定義具體構件(Concrete Component)角色 ,就是炒飯,炒麵
package study.wyy.design.decorator.after;

/**
 * @author by wyaoyao
 * @Description: 炒飯
 * @Date 2020/12/13 8:26 上午
 */
public class FriedRice extends FastFood{
    public FriedRice(String name, float price) {
        super(name, price);
    }
    public FriedRice() {
        // 普通價格為12 
        super("炒飯",12);
    }
}
package study.wyy.design.decorator.after;

/**
 * @author by wyaoyao
 * @Description
 * @Date 2020/12/13 8:29 上午
 */
public class FriedNoodles extends FastFood{

    public FriedNoodles(String name, float price) {
        super(name, price);
    }

    public FriedNoodles() {
        // 普通炒麵為10
        super("炒麵",10);
    }
}
  1. 定義抽象裝飾(Decorator)角色 ,對雞蛋,培根等配料進行抽象,繼承快餐類,這樣才可以通過裝飾者對快餐類的cost方法進行增強(根據加蛋的不同進行計算價格)
package study.wyy.design.decorator.after;

import lombok.Getter;
import lombok.Setter;

/**
 * @author by wyaoyao
 * @Description
 * @Date 2020/12/13 8:32 上午
 * 配料
 */
public abstract class Garnish extends FastFood{

    @Setter @Getter
    private FastFood fastFood;

    @Setter @Getter
    /****
     * 配料的數量:比如加蛋的數量,預設一個
     */
    private int num = 1;
    public Garnish(FastFood fastFood,String name,float price) {
        super(name,price);
        this.fastFood = fastFood;
    }
}
  1. 具體裝飾(ConcreteDecorator)角色
package study.wyy.design.decorator.after;

/**
 * @author by wyaoyao
 * @Description
 * @Date 2020/12/13 9:00 上午
 */
public class Egg extends Garnish{


    public Egg(FastFood fastFood,int num) {
        // 加蛋一塊
        super(fastFood, "雞蛋", 1);
        super.setNum(num);
    }

    public Egg(FastFood fastFood) {
        // 加蛋一塊
        super(fastFood, "雞蛋", 1);
    }

    @Override
    public float cost() {
        // 加蛋數量 * 雞蛋價錢 + 基本價錢
        return getPrice() * getNum()+ super.getFastFood().getPrice();
    }

    @Override
    public String getName() {
        return "加蛋" + super.getFastFood().getName();
    }
}
package study.wyy.design.decorator.after;

/**
 * @author by wyaoyao
 * @Description
 * @Date 2020/12/13 9:00 上午
 */
public class Bacon extends Garnish{


    public Bacon(FastFood fastFood, int num) {
        // 培根2元
        super(fastFood, "培根", 2);
        super.setNum(num);
    }

    public Bacon(FastFood fastFood) {
        // 加培2元
        super(fastFood, "培根", 2);
    }

    @Override
    public float cost() {
        // 加培根數量 * 培根價錢 + 基本價錢
        return getPrice() * getNum()+ super.getFastFood().getPrice();
    }

    @Override
    public String getName() {
        return "加培根" + super.getFastFood().getName();
    }
}
  1. 測試
package study.wyy.design.decorator.after;

/**
 * @author by wyaoyao
 * @Description
 * @Date 2020/12/13 9:05 上午
 */
public class Test {

    public static void main(String[] args) {
        // 炒飯
        FastFood friedRice = new FriedRice();
        System.out.println(friedRice.getName() + "  " + friedRice.cost() + "元");

        // 加兩個雞蛋炒飯
        Egg egg = new Egg(friedRice, 2);
        System.out.println(egg.getName() + "  " + egg.cost() + "元" + "  " + "加蛋"+egg.getNum()+"個");

        // 加3個培根炒飯
        Bacon bacon = new Bacon(friedRice, 3);
        System.out.println(bacon.getName() + "  " + bacon.cost() + "元" + "  " + "加蛋"+bacon.getNum()+"個");

    }
}
炒飯  12.0元
加蛋炒飯  14.0元  加蛋2個
加培根炒飯  18.0元  加蛋3

類圖
在這裡插入圖片描述

  • 飾者模式可以帶來比繼承更加靈活性的擴充套件功能,使用更加方便,可以通過組合不同的裝飾者物件來獲取具有不同行為狀態的多樣化的結果。裝飾者模式比繼承更具良好的擴充套件性,完美的遵循開閉原則,繼承是靜態的附加責任,裝飾者則是動態的附加責任。

  • 裝飾類和被裝飾類可以獨立發展,不會相互耦合,裝飾模式是繼承的一個替代模式,裝飾模式可以動態擴充套件一個實現類的功能。

這個時候我們在增加炒河粉的時候,只需要增加一個河粉類即可,實現配料和快餐的隔離,如果加其他的配料也是如此

3 總結

3.1 使用場景

  • 當不能採用繼承的方式對系統進行擴充或者採用繼承不利於系統擴充套件和維護時。

    不能採用繼承的情況主要有兩類:

    • 第一類是系統中存在大量獨立的擴充套件,為支援每一種組合將產生大量的子類,使得子類數目呈爆炸性增長;
    • 第二類是因為類定義不能繼承(如final類)
  • 在不影響其他物件的情況下,以動態、透明的方式給單個物件新增職責。

  • 當物件的功能要求可以動態地新增,也可以再動態地撤銷時。

3.2 靜態代理和裝飾者的區別

靜態代理和裝飾者模式的區別:

  • 相同點:
    • 都要實現與目標類相同的業務介面
    • 在兩個類中都要宣告目標物件
    • 都可以在不修改目標類的前提下增強目標方法
  • 不同點:
    • 目的不同
      裝飾者是為了增強目標物件
      靜態代理是為了保護和隱藏目標物件
    • 獲取目標物件構建的地方不同
      裝飾者是由外界傳遞進來,可以通過構造方法傳遞
      靜態代理是在代理類內部建立,以此來隱藏目標物件

相關文章