天天看點

設計模式—— 十七:裝飾器模式什麼是裝飾器模式?為什麼要用裝飾器模式裝飾器模式優缺點裝飾器模式應用場景擴充-Java中的裝飾器模式

文章目錄

裝飾器模式的定義:

Attach additional responsibilities to an object dynamically keeping the same interface.Decorators provide a flexible alternative to subclassing for extending functionality.(動态地給一個對象添加一些額外的職責。 就增加功能來說,裝飾模式相比生成子類更為靈活。)

裝飾模式的通用類圖如圖17-1所示。

圖17-1:裝飾器模式通用類圖

設計模式—— 十七:裝飾器模式什麼是裝飾器模式?為什麼要用裝飾器模式裝飾器模式優缺點裝飾器模式應用場景擴充-Java中的裝飾器模式

在裝飾器模式類圖中包含如下幾個角色:

● Component(抽象構件)

抽象構件它是具體構件和抽象裝飾類的共同父類,聲明了在具體構件中實作的業務方法。

● ConcreteComponent (具體構件)

具體構件 ConcreteComponent是最核心、最原始、最基本的接口或抽象類的實作,要裝飾的就是它。

● Decorator(抽象裝飾類)

抽象裝飾類也是抽象構件類的子類,用于給具體構件增加職責,但是具體職責在其子類中實作。它維護一個指向抽象構件對象的引用,通過該引用可以調用裝飾之前構件對象的方法,并通過其子類擴充該方法,以達到裝飾的目的。

● ConcreteDecorator(具體裝飾類)

具體裝飾類是抽象裝飾類的子類,負責向構件添加新的職責。每 一個具體裝飾類都定義了一些新的行為,它可以調用在抽象裝飾類中定義的方法,并可以增加新的方法用以擴充對象的行為。

下面來看一下具體的代碼實作:

  • 抽象構件:
/**
 * @author 三分惡
 * @date 2020年7月10日 
 * @description  抽象構件
 */
public abstract class Component {
    //抽象的方法 
    public abstract void operate();
}      
  • 具體構件:
/**
 * @author 三分惡
 * @date 2020年7月10日 
 * @description  具體構件
 */
public class ConcreteComponent extends Component{

    //具體實作
    @Override
    public void operate() {
        System.out.println("do Something");
    }

}      
  • 抽象裝飾器:
/**
 * @author 三分惡
 * @date 2020年7月10日
 * @description 抽象裝飾者
 */
public abstract class Decorator extends Component{
    private Component component = null;

    //通過構造函數傳遞被修飾者 
    public Decorator(Component _component) {
        this.component = _component;
    }
    
    //委托給被裝飾者執行
    @Override
    public void operate() {
        this.component.operate();
    }
}      
  • 具體裝飾者:
public class ConcreteDecorator1 extends Decorator {

    /**
     * 定義被裝飾者
     * 
     * @param _component
     */
    public ConcreteDecorator1(Component _component) {
        super(_component);
    }

    // 定義自己的修飾方法
    private void method1() {
        System.out.println("method1 修飾");
    }
    
    //重寫父類的operate方法
    @Override
    public void operate() {
        this.method1();
        super.operate();
    }

}


public class ConcreteDecorator2 extends Decorator {

    /**
     * 定義被裝飾者
     * 
     * @param _component
     */
    public ConcreteDecorator2(Component _component) {
        super(_component);
    }

    // 定義自己的修飾方法
    private void method2() {
        System.out.println("method2 修飾");
    }
    
    //重寫父類的operate方法
    @Override
    public void operate() {
        this.method2();
        super.operate();
    }

}      
  • 場景類:通過Client類來模拟高層子產品的耦合關系,看看裝飾模式是如何運作的
public class Client {

    /**
     * @param args
     */
    public static void main(String[] args) {
        Component component=new ConcreteComponent();
        //第一次裝飾
        component=new ConcreteDecorator1(component);
        //第二次裝飾
        component=new ConcreteDecorator2(component);
        //執行operate方法
        component.operate();
    }

}      

運作結果:

設計模式—— 十七:裝飾器模式什麼是裝飾器模式?為什麼要用裝飾器模式裝飾器模式優缺點裝飾器模式應用場景擴充-Java中的裝飾器模式

上面已經引入了裝飾器的模式的定義,那麼為什麼要用裝飾器模式呢?

有時我們希望給某個對象而不是整個類添加一些功能,例如:一個圖形使用者界面工具箱允許你對任意一個使用者界面元件添加一些特性,例如邊框。

使用繼承機制是添加功能的一種有效途徑,但這種方法不夠靈活,因為邊框的選擇是靜态的,使用者不能控制對元件加邊框的方式和時機。

下面是一個具體的業務場景執行個體。

有一個咖啡店,銷售各種各樣的咖啡,拿鐵,卡布奇洛,藍山咖啡等,在沖泡前,會詢問顧客是否要加糖,加奶,加薄荷等。這樣不同的咖啡配上不同的調料就會賣出不同的價格。

基于這個業務場景,首先設計一個抽象的咖啡類,然後每種咖啡不同的實作。

設計模式—— 十七:裝飾器模式什麼是裝飾器模式?為什麼要用裝飾器模式裝飾器模式優缺點裝飾器模式應用場景擴充-Java中的裝飾器模式
  • 抽象咖啡類:
public abstract class Coffee {
    // 是否加了牛奶
    protected boolean addedMilk;
    // 是否加了糖
    protected boolean addedSugar;
    // 是否加了薄荷
    protected boolean addedMint;

    /**
     * 擷取咖啡得名字
     */
    public abstract String getName();

    /**
     * 擷取咖啡的價格
     */
    public abstract double getPrice();
}
      
  • 實作之一:藍山咖啡
public class BuleCoffee extends Coffee {
    @Override
    public String getName() {
        StringBuilder name = new StringBuilder();
        name.append("藍山");
        if (addedMilk) {
            name.append("牛奶");
        }
        if (addedMilk) {
            name.append("薄荷");
        }
        if (addedSugar) {
            name.append("加糖");
        }
        return name.toString();
    }

    @Override
    public double getPrice() {
        double price = 10;
        if (addedMilk) {
            price += 1.1;
        }
        if (addedMilk) {
            price += 3.2;
        }
        if (addedSugar) {
            price += 2.7;
        }

        return price;
    }
}
      

OK,業務已經實作,但是存在什麼問題呢?

  • 擴充麻煩:拿鐵一種實作,加糖拿鐵一種實作,加牛奶加糖又是一種實作
  • 代碼重複:存在一些重複的加糖、加牛奶的代碼
  • 系統龐大:咖啡裡每加一種東西就要有一個新的實作,到最後實作類會非常多。

是以,該考慮引入裝飾器模式了。

設計出一個抽象裝飾類,它也繼承自coffee,具體的裝飾類,繼承抽象裝飾類。

設計模式—— 十七:裝飾器模式什麼是裝飾器模式?為什麼要用裝飾器模式裝飾器模式優缺點裝飾器模式應用場景擴充-Java中的裝飾器模式
  • 抽象咖啡裝飾類:
public abstract class CoffeeDecorator extends Coffee {
    private Coffee delegate;

    public CoffeeDecorator(Coffee coffee) {
        this.delegate = coffee;
    }

    @Override
    public String getName() {
        return delegate.getName();
    }

    @Override
    public double getPrice() {
        return delegate.getPrice();
    }
}
      
  • MilkCoffeeDecorator:
public class MilkCoffeeDecorator extends CoffeeDecorator {
    public MilkCoffeeDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getName() {
        return "牛奶, " + super.getName();
    }

    @Override
    public double getPrice() {
        return 1.1 + super.getPrice();
    }
}
      

類似地實作出MintCoffeeDecorator,SugarCoffeeDecorator。

  • Client類:
public class Client {
    public static void main(String[] args) {
        // 得到一杯原始的藍山咖啡
        Coffee blueCoffee = new BlueCoffee();
        System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());

        // 加入牛奶
        blueCoffee = new MilkCoffeeDecorator(blueCoffee);
        System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());

        // 再加入薄荷
        blueCoffee = new MintCoffeeDecorator(blueCoffee);
        System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());

        // 再加入糖
        blueCoffee = new SugarCoffeeDecorator(blueCoffee);
        System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());
    }
}
      

● 裝飾類和被裝飾類可以獨立發展,而不會互相耦合。換句話說,Component類無須知 道Decorator類,Decorator類是從外部來擴充Component類的功能,而Decorator也不用知道具 體的構件。

● 裝飾模式是繼承關系的一個替代方案。我們看裝飾類Decorator,不管裝飾多少層,返 回的對象還是Component,實作的還是is-a的關系。

● 裝飾模式可以動态地擴充一個實作類的功能,這不需要多說,裝飾模式的定義就是如此。

● 使用裝飾模式進行系統設計時将産生很多小對象,這些對象的差別在于它們之間互相連接配接 的方式有所不同,而不是它們的類或者屬性值有所不同,大量小對象的産生勢必會占用更多 的系統資源,在一定程式上影響程式的性能。

● 裝飾模式提供了一種比繼承更加靈活機動的解決方案,但同時也意味着比繼承更加易于出 錯,排錯也很困難,對于多次裝飾的對象,調試時尋找錯誤可能需要逐級排查,較為繁瑣。

● 需要擴充一個類的功能,或給一個類增加附加功能。

● 需要動态地給一個對象增加功能,這些功能可以再動态地撤銷。

● 需要為一批的兄弟類進行改裝或加裝功能,當然是首選裝飾模式。

在Java中比較典型的應用就是I/O流。

以下是Java I/O流InputStream的部分類圖:

設計模式—— 十七:裝飾器模式什麼是裝飾器模式?為什麼要用裝飾器模式裝飾器模式優缺點裝飾器模式應用場景擴充-Java中的裝飾器模式

通過圖中可以看出:

● 抽象構件(Component)角色:由InputStream扮演。這是一個抽象類,為各種子類型提供統一的接口。

● 具體構件(ConcreteComponent)角色:由ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream等類扮演。它們實作了抽象構件角色所規定的接口。

● 抽象裝飾(Decorator)角色:由FilterInputStream扮演。它實作了InputStream所規定的接口。

● 具體裝飾(ConcreteDecorator)角色:由幾個類扮演,分别是BufferedInputStream、DataInputStream以及兩個不常用到的類LineNumberInputStream、PushbackInputStream。