1. 程式人生 > 實用技巧 >設計模式:裝飾器模式

設計模式:裝飾器模式

裝飾器模式概述

裝飾器模式,也稱為包裝器模式,指在不改變原有物件的基礎上,動態給一個物件新增一個額外的職責。

場景一

星巴克裡面賣多種飲料,拿鐵,咖啡,每一種飲料都有自己的價格。

為解決需求,設計一個飲料抽象類:

public abstract class Beverage {

    private String name;
    
    private int cost;

    public void setName() {
        this.name = "飲料";
    }

    String getName() {
        return name;
    }

    Beverage(String name) {
        this.name = name;
    }

    //價格
    public int cost(){
        return cost;
    }
}

具體飲料:拿鐵和咖啡繼承飲料抽象

public class Coffee extends Beverage{

    public Coffee() {
        super("咖啡");
    }

    @Override
    public int cost() {
        return 10;
    }
}

public class Latte extends Beverage{

    public Latte() {
        super("拿鐵");
    }

    @Override
    public int cost() {
        return 12;
    }
}

測試:

    public static void main(String[] args) {
        Beverage beverage = new Coffee();
        Beverage beverage2 = new Latte();

        System.out.println(beverage.getName()+" : "+beverage.cost());
        System.out.println(beverage2.getName()+" : "+beverage2.cost());
    }

場景二

現在需求變化了,現在星巴克不僅有拿鐵,咖啡,可能在拿鐵和咖啡中加入巧克力,糖,堅果,花生等調料,並且每份調料分別計算價格。

那我們可以創建出 巧克力拿鐵糖拿鐵堅果拿鐵花生拿鐵巧克力糖拿鐵巧克力花生拿鐵等等類,會造成類爆炸的情況,996程式設計師也寫不完,007也難,並且每加一種調料,工作量也是成倍增加。

所以我們改進寫法:

把每一份調料作為成員變數放入飲料中。修改飲料類:

public abstract class Beverage {

    private boolean chocolate = false;//巧克力

    private boolean sugar  = false;//糖

    private boolean nut  = false;//堅果

    private boolean peanut  = false;//花生

    private String name;

    private int cost;

    public void setName() {
        this.name = "飲料";

    }

    String getName() {
        if(this.chocolate){
            name = name + "+巧克力";
        }
        if(this.sugar){
            name = name + "+糖";
        }
        if(this.nut){
            name = name + "+堅果";
        }
        if(this.peanut){
            name = name + "+花生";
        }
        return name;
    }

    Beverage(String name) {
        this.name = name;
    }

    //價格
    public int cost(){
        if(this.chocolate){
            cost = cost + 2;
        }
        if(this.sugar){
            cost = cost + 3;
        }
        if(this.nut){
            cost = cost + 4;
        }
        if(this.peanut){
            cost = cost + 5;
        }
        return cost;
    }

    public void setChocolate(boolean chocolate) {
        this.chocolate = chocolate;
    }

    public void setSugar(boolean sugar) {
        this.sugar = sugar;
    }

    public void setNut(boolean nut) {
        this.nut = nut;
    }

    public void setPeanut(boolean peanut) {
        this.peanut = peanut;
    }
}

以咖啡類為例,修改cost方法:

public class Coffee extends Beverage {

    public Coffee() {
        super("咖啡");
    }

    @Override
    public int cost() {
        return 10 + super.cost();
    }
}

測試:

    public static void main(String[] args) {
        Beverage beverage = new Coffee();
        beverage.setChocolate(true);
        beverage.setNut(true);

        System.out.println(beverage.getName()+" : "+beverage.cost());
    }

需求我們完美實現了,飲料類中加多份調料,並計算出價格。

場景三

  • 業務一:隨著星巴克業務的變化和客戶的需求,需要加入奶油這一調料。我們這時候勢必需要修改飲料類的cost方法和getName方法,而我們作為客戶端是不能修改程式碼的,這違反了開閉原則。
  • 業務二:有的客戶需要在咖啡中加入兩份巧克力,兩份巧克力總不能和一份的一樣價錢吧!而我們寫的成員變數作為布林值,無法體現出同一調料的數量,勢必需要改變原始碼以適應新的需求,又違反開閉原則。

這時候,救世主“裝飾器模式”誕生了!

重新修改飲料類,不再需要各種調料判斷:

public abstract class Beverage {

    private String name;

    private int cost;

    public void setName() {
        this.name = "飲料";

    }
    String getName() {

        return name;
    }

    Beverage(String name) {
        this.name = name;
    }

    //價格
    public abstract int cost();
}

並抽象出調料類:

//調料
public abstract class Seasoning extends Beverage{//1.繼承Beverage

    protected Beverage beverage;//2.需要有一個Beverage的成員變數

    Seasoning(Beverage beverage) {
        super("調料");
        this.beverage = beverage;
    }
}

寫兩個調料的實現類:巧克力和糖(花生類和堅果類不寫了)

public class Chocolate extends Seasoning{

    private int cost = 2;

    Chocolate(Beverage beverage){
        super(beverage);
    }

    @Override
    public int cost() {
        return beverage.cost() + this.cost;
    }

    @Override
    String getName() {
        return beverage.getName() + "+巧克力";
    }
}

public class Sugar extends Seasoning{

    private int cost = 3;

    Sugar(Beverage beverage) {
        super(beverage);
    }

    @Override
    public int cost() {
        return beverage.cost() + this.cost;
    }

    @Override
    String getName() {
        return beverage.getName() + "+糖";
    }
}

測試:

    public static void main(String[] args) {
        Beverage beverage ;
        beverage  = new Coffee();

        beverage = new Sugar(beverage);//這裡一層裝飾一層(包粽子一樣)
        beverage = new Chocolate(beverage);
        beverage = new Sugar(beverage);
        beverage = new Chocolate(beverage);
        beverage = new Chocolate(beverage);

        System.out.println(beverage.getName()+" : "+beverage.cost());
    }

測試結果:

這裡,我們實現了上述需求。以後無論增加什麼調料或者飲料,我們都只需要繼承Seasoning或者Beverage,調料如何加,加幾份,我們也能實現,並且無需修改原始碼。上述就是裝飾器模式的具體實現

裝飾器模式可以靈活擴充套件,但所有類都來自同一個祖宗Beverage。

uml類圖:

裝飾器模式的通用寫法

//抽象元件
public abstract class Component {
    public abstract void operation();
}

//裝飾器抽象
public abstract class Decorator extends Component{

    protected Component component;

    public Decorator(Component component) {
        this.component = component;
    }

    public void operation(){
        component.operation();
    }
}

//具體元件
public class ConcreteComponent extends Component{
    @Override
    public void operation() {
        System.out.println("ConcreteComponent operation");
    }
}

//具體裝飾器
public class ConcreteDecorator extends Decorator{
    public ConcreteDecorator(Component component) {
        super(component);
    }

    public void operation(){
        System.out.println("first");
        super.operation();
        System.out.println("last");
    }
}

測試:

    public static void main(String[] args) {
        Component component = new ConcreteDecorator(new ConcreteComponent());
        component.operation();
    }

jdk中的裝飾器模式

jdk中的io相關的流所用的就是最明顯的裝飾器模式。InputStream就是我們上述的飲料,而FilterInputStream就是我們上述的調料。