裝飾器模式
一. 什麼是裝飾器模式?
我們都知道裝飾, 元旦, 聖誕節, 我們都需要裝飾, 渲染節日氣氛. . 所謂裝飾, 就是在原來的基礎上加東西.
裝飾器模式(Decorator Pattern)允許向一個現有的物件新增新的功能,同時又不改變其結構。這種型別的設計模式屬於結構型模式,它是對現有類的包裝。
這種模式建立了一個裝飾類,用來包裝原有的類,並在保持類方法簽名完整性的前提下,提供了額外的功能。
我們通過下面的例項來演示裝飾器模式的用法。其中,我們將把聖誕節的房間在原來的基礎上裝飾上了氣球。
我們來看看UML圖:
最開始有房子, 房子有平房, 樓房, 別墅, 我們會打掃房間.
/**
* 房子抽象類
*/
public interface IHouse {
void clean();
}
/**
* 平房
*/
public class Bungalow implements IHouse{
@Override
public void clean() {
// 打掃房間
}
}
/**
* 樓房
*/
public class HighRiseBuilding implements IHouse{
@Override
public void clean() {
// 打掃房間
}
}
/**
* 別墅
*/
public class Villa implements IHouse{
@Override
public void clean() {
// 打掃房間
}
}
這時, 遇到節假日, 比如:聖誕節, 元旦, 我們會裝飾房間, 給房間佈置氣球等.
我們可以採用裝飾器設計模式. 這時, 裝飾的主體依然房子. 裝飾就像是一個殼子, 套在房子的外面.
/**
* 房子抽象類
*/
public interface IHouse {
void clean();
}
/**
* 平房
*/
public class Bungalow implements IHouse{
@Override
public void clean() {
// 打掃房間
System.out.println("我是平房: 打掃房間");
}
}
/**
* 樓房
*/
public class HighRiseBuilding implements IHouse{
@Override
public void clean() {
// 打掃房間
System.out.println("我是樓房: 打掃房間");
}
}
/**
* 別墅
*/
public class Villa implements IHouse{
@Override
public void clean() {
// 打掃房間
System.out.println("我是別墅: 打掃房間");
}
}
/**
* 房間裝飾器
*/
public abstract class DecoratorHouse implements IHouse {
// 被裝飾的房子
protected IHouse decoratedHouse;
public DecoratorHouse(IHouse decoratedHouse) {
this.decoratedHouse = decoratedHouse;
}
}
public class BalloonDecoratorHouse extends DecoratorHouse {
public BalloonDecoratorHouse(IHouse decoratedHouse) {
super(decoratedHouse);
}
@Override
public void clean() {
// 打掃房間
super.decoratedHouse.clean();
ballonDecorate();
}
public void ballonDecorate() {
// 用氣球裝飾房間
System.out.println("用氣球裝飾房間, 好漂亮");
}
}
public class DecoratorPatternTest {
public static void main(String[] args) {
IHouse house = new Villa();
house.clean();
System.out.println("-------聖誕節到了, 裝飾房間-------");
IHouse decoratorHouse = new BalloonDecoratorHouse(new Villa());
decoratorHouse.clean();
}
}
使用裝飾器模式以後, 我們在不改變原有房子的基礎上進行了擴充套件. 節假日, 就可以使用裝飾類裝飾房間, 如果節日過去了, 我們就繼續使用原來的房子.
接下來, 我們來總結一些上面的案例.
首先, 我們有一個元件Component, 在這個元件裡有一些自定義的功能. 通常這個元件Component是抽象的(介面或者抽象類)
然後, 抽象類會有一個具體的實現 ConcreteComponent, 除了實現元件Component的方法, 還可自定義方法.
第三, 我們現在想要對具體實現ConcreteComponent進行包裝, 定義一個包裝類 DecoratorComponent. 通常這個包裝類是抽象的. 包裝類也實現元件Component介面, 然後引入一個Component的具體的成員變數. 為什麼要引入成員變數呢? 這個也很好理解, 我們的目標是包裝的具體類.
第四, 定義一個具體的裝飾器 DecoratorComponent, 再具體的裝飾器中, 可以增加額外的方法. 比如在之前後者之後增加一些邏輯. 具體關係圖如下:
程式碼實現如下:
/**
* 抽象功能
*/
public interface Component {
void operate();
}
/**
* 具體功能實現
*/
public class ConcreteComponent implements Component{
@Override
public void operate() {
// 實現具體的邏輯
System.out.println("具體實現的邏輯");
}
}
/**
* 用來裝飾Component物件的類
*/
public abstract class DeceratorComponent implements Component{
// 指定裝飾的物件
protected Component deceratedComponent;
public DeceratorComponent(Component deceratedComponent) {
this.deceratedComponent = deceratedComponent;
}
}
/**
* 具體的裝飾類
*/
public class ConcreteDeceratorComponent extends DeceratorComponent {
public ConcreteDeceratorComponent(Component deceratedComponent) {
super(deceratedComponent);
}
@Override
public void operate() {
before();
super.deceratedComponent.operate();
after();
}
public void before(){
System.out.println("在原邏輯之前增加了邏輯");
}
public void after(){
System.out.println("在原邏輯之後增加了邏輯");
}
}
public class DeceratorTest {
public static void main(String[] args) {
Component concreteCom = new ConcreteComponent();
concreteCom.operate();
System.out.println("============");
DeceratorComponent decerator = new ConcreteDeceratorComponent(new ConcreteComponent());
decerator.operate();
}
}
執行結果:
具體實現的邏輯
============
在原邏輯之前增加了邏輯
具體實現的邏輯
在原邏輯之後增加了邏輯
設計模式的靈活應用. 假如當前具體類就只有一個. 我們就不需要定義抽象的Component了. 如何實現裝飾模式呢?
那就讓裝飾器直接繼承自原來的類就可以了:
二. 裝飾器模式的特點:
- 裝飾物件和真實物件有相同的介面。這樣客戶端物件就能以和真實物件相同的方式和裝飾物件互動。
- 裝飾物件包含一個真實物件的引用(reference)
- 裝飾物件接受所有來自客戶端的請求。它把這些請求轉發給真實的物件。
- 裝飾物件可以在轉發這些請求以前或以後增加一些附加功能。這樣就確保了在執行時,不用修改給定物件的結構就可以在外部增加附加的功能。在物件導向的設計中,通常是通過繼承來實現對給定類的功能擴充套件。
三. 裝飾器模式的使用場景:
以下情況可以考慮使用裝飾器模式
- 需要擴充套件一個類的功能,或給一個類新增附加職責。
- 需要動態的給一個物件新增功能,這些功能可以再動態的撤銷。
- 需要增加由一些基本功能的排列組合而產生的非常大量的功能,從而使繼承關係變的不現實。
- 當不能採用生成子類的方法進行擴充時。一種情況是,可能有大量獨立的擴充套件,為支援每一種組合將產生大量的子類,使得子類數目呈爆炸性增長。另一種情況可能是因為類定義被隱藏,或類定義不能用於生成子類。
四. 裝飾器模式的優點:
- Decorator模式與繼承關係的目的都是要擴充套件物件的功能,但是Decorator可以提供比繼承更多的靈活性。
- 通過使用不同的具體裝飾類以及這些裝飾類的排列組合,設計師可以創造出很多不同行為的組合。
五. 裝飾器模式的缺點
- 這種比繼承更加靈活機動的特性,也同時意味著更加多的複雜性。
- 裝飾模式會導致設計中出現許多小類,如果過度使用,會使程式變得很複雜。
- 裝飾模式是針對抽象元件(Component)型別程式設計。但是,如果你要針對具體元件程式設計時,就應該重新思考你的應用架構,以及裝飾者是否合適。當然也可以改變Component介面,增加新的公開的行為,實現“半透明”的裝飾者模式。在實際專案中要做出最佳選擇。
六. 思考: 裝飾器模式使用了哪些設計模式的原則?
- 最明顯的體現就是開閉原則---對擴充套件開發, 對修改關閉. 當新的需求到達, 在不改變原來功能的基礎上進行擴充套件.
- 依賴倒置原則---依賴於抽象, 而非具體. 方便擴充套件, 再來一種新房子, 不用修改房子裝飾類.
- 單一職責原則: 一個類只負責一件事