設計模式詳解——裝飾者模式
前言
在程式開發設計中,有一個特別重要的原則是,類應該對擴充套件開放,對修改關閉,雖然這一原則聽起來很矛盾,但是在一些比較優秀的設計模式中,是完全可以達成這一原則的,比如裝飾者模式,它就是這一原則的最佳實踐,下面我們來看下它的基本原理和用法,希望能通過這篇內容,引發給位小夥伴對於設計模式的思考。
裝飾者模式
裝飾者模式動態地將責任附加到物件上,若要擴充套件功能,裝飾者提供了比繼承更有彈性地替代方案。
要點
- 繼承屬於擴充套件形式之一,但不見得是達到彈性設計的最佳方式;
- 在我們的設計中,應該允許行為可以被擴充套件,而無須修改現有程式碼
- 組合和委託可用於在執行時動態地加上新的行為
- 除了繼承,裝飾者模式也可以讓我們擴充套件行為
- 裝飾者模式意味著一群裝飾者類,這些類用來包裝具體元件
- 裝飾者類反映出被裝飾元件地型別(他們具有相同的型別,都經過介面或繼承實現)
- 裝飾者可以在被裝飾者地行為前面或者後面加上自己的行為,甚至將被裝飾者的行為整體覆蓋,從而達到特定目的
- 裝飾者一般對元件的客戶是透明的,除非客戶程式依賴於元件的具體型別
- 裝飾者會導致設計中出現許多小物件,如果過度使用,會讓程式變得複雜。
示例程式碼
原始物件介面
基類介面,也是包裝類最底層的內容(俄羅斯套娃裡面第一個被套的那個),也可以理解為包裝類中的最小單位,這裡我們用形狀這個抽象概念作為示例演示。
首先這個抽象介面有一個基本方法,就是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
有好多地方也用到了這種設計模式,最典型的是ServletRequest
和ServletResponse
。