1. 程式人生 > 其它 >設計模式詳解——裝飾者模式

設計模式詳解——裝飾者模式

前言

在程式開發設計中,有一個特別重要的原則是,類應該對擴充套件開放,對修改關閉,雖然這一原則聽起來很矛盾,但是在一些比較優秀的設計模式中,是完全可以達成這一原則的,比如裝飾者模式,它就是這一原則的最佳實踐,下面我們來看下它的基本原理和用法,希望能通過這篇內容,引發給位小夥伴對於設計模式的思考。

裝飾者模式

裝飾者模式動態地將責任附加到物件上,若要擴充套件功能,裝飾者提供了比繼承更有彈性地替代方案。

要點

  • 繼承屬於擴充套件形式之一,但不見得是達到彈性設計的最佳方式;
  • 在我們的設計中,應該允許行為可以被擴充套件,而無須修改現有程式碼
  • 組合和委託可用於在執行時動態地加上新的行為
  • 除了繼承,裝飾者模式也可以讓我們擴充套件行為
  • 裝飾者模式意味著一群裝飾者類,這些類用來包裝具體元件
  • 裝飾者類反映出被裝飾元件地型別(他們具有相同的型別,都經過介面或繼承實現)
  • 裝飾者可以在被裝飾者地行為前面或者後面加上自己的行為,甚至將被裝飾者的行為整體覆蓋,從而達到特定目的
  • 裝飾者一般對元件的客戶是透明的,除非客戶程式依賴於元件的具體型別
  • 裝飾者會導致設計中出現許多小物件,如果過度使用,會讓程式變得複雜。

示例程式碼

原始物件介面

基類介面,也是包裝類最底層的內容(俄羅斯套娃裡面第一個被套的那個),也可以理解為包裝類中的最小單位,這裡我們用形狀這個抽象概念作為示例演示。

首先這個抽象介面有一個基本方法,就是draw繪製方法,凡是實現了形狀這個介面的所有類都必須實現這個介面。

public interface Shape {
    /**
     * 繪製方法
     */
    void draw();
}
介面實現

這裡是介面的實現,不過為了方便理解,各位小夥伴可以把介面的實現當作第一層包裝類,這一層包裝相當於直接確定了我們形狀的基本屬性,表明它是一個圓形。

public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("繪製圓形");
    }
}

同理,這裡確定了我們的形狀為矩形。

public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("繪製矩形");
    }
}
裝飾物件抽象

這裡相當於做一個通用的包裝介面,後續的包裝類只需要繼承這個抽象類,然後再在其中加入新的行為或屬性即可,而不需要重複封裝。

public abstract class ShapeDecorator implements Shape {
    protected Shape decoratedShape;

    public ShapeDecorator(Shape decoratedShape){
        this.decoratedShape = decoratedShape;
    }

    public void draw(){
        decoratedShape.draw();
    }
}
裝飾者實現

這裡就是第一層包裝(如果按我們上面的說法應該是第三層,因為我們傳遞的肯定是shape的實現,而不是抽象介面),這裡繼承了上面的抽象包裝類。

public class RedShapeDecorator extends ShapeDecorator {

    public RedShapeDecorator(Shape decoratedShape) {
        super(decoratedShape);
    }

    @Override
    public void draw() {
        decoratedShape.draw();
        setRedBorder(decoratedShape);
    }

    private void setRedBorder(Shape decoratedShape){
        System.out.println("Border Color: Red");
    }
}

同時引入了一個新的方法setRedBorder,這個方法讓我們的形狀有了一個新的屬性——紅色。當然,我們還可以繼續封裝,我們可以在顏色的基礎上給他封裝一個尺寸:

public class SizeShapeDecorator extends RedShapeDecorator{

    public SizeShapeDecorator(Shape decoratedShape) {
        super(decoratedShape);
    }

    @Override
    public void draw() {
        super.draw();
        setSize(decoratedShape);
    }

    private void setSize(Shape decoratedShape) {
        System.out.println("size: 100");
    }
}
測試程式碼

這裡我們分別建立一個圓形和矩形,指定不同的包裝類,並呼叫他們的draw方法:

	@Test
    public void testDecorator() {
        Shape circle = new Circle();
        ShapeDecorator redCircle = new RedShapeDecorator(new Circle());
        ShapeDecorator redRectangle = new RedShapeDecorator(new Rectangle());
        ShapeDecorator sizeRedRectangle = new SizeShapeDecorator(new Rectangle());
        System.out.println("原始圓形");
        circle.draw();

        System.out.println("===========\n包裝的紅色圓形");
        redCircle.draw();

        System.out.println("===========\n包裝的紅色矩形");
        redRectangle.draw(); 
        
        System.out.println("===========\n包裝的100紅色矩形");
        sizeRedRectangle.draw();
    }

最後執行結果如下:

總結

從示例程式碼及執行結果我們可以看出來,雖然後續我們不斷為原始的形狀加入了新的屬性,但是我們並沒有改變原始程式碼,而是一層一層地新增不同的包裝類,然後讓它逐步有了更多的新屬性。

這樣的操作好處是,我們原有的形狀擁有了新的屬性,但這些後增加的屬性和原始形狀之間並沒有任何強依賴關係,也沒有破壞原因業務邏輯,保證了系統的可擴充套件性,同時耦合性還很低,這就是裝飾者設計模式的魅力。

不知道各位小夥伴是否還記得我們前段時間分享的spring boot原始碼相關內容,spring boot的原始碼中就用到了裝飾者模式,比如BeanFactory的相關內容,另外我們日常開發過程中用到的Tomcat有好多地方也用到了這種設計模式,最典型的是ServletRequestServletResponse