在現實生活中,常常需要對現有產品增加新的功能或美化其外觀,如房子裝修、相片加相框等。在軟件開發過程中,有時想用一些現存的組件。這些組件可能只是完成了一些核心功能。但在不改變其結構的情況下,可以動態地擴展其功能。所有這些都可以釆用裝飾模式來實現。
1. 裝飾模式的定義與特點
裝飾(Decorator)模式的定義:指在不改變現有對象結構的情況下,動態地給該對象增加一些職責(即增加其額外功能)的模式,它屬於對象結構型模式。
裝飾(Decorator)模式的主要優點有:
- 採用裝飾模式擴展對象的功能比採用繼承方式更加靈活。
- 可以設計出多個不同的具體裝飾類,創造出多個不同行爲的組合。
其主要缺點是:裝飾模式增加了許多子類,如果過度使用會使程序變得很複雜。
2. 裝飾模式的結構與實現
通常情況下,擴展一個類的功能會使用繼承方式來實現。但繼承具有靜態特徵,耦合度高,並且隨着擴展功能的增多,子類會很膨脹。如果使用組合關係來創建一個包裝對象(即裝飾對象)來包裹真實對象,並在保持真實對象的類結構不變的前提下,爲其提供額外的功能,這就是裝飾模式的目標。下面來分析其基本結構和實現方法。
2.1 模式的結構
裝飾模式主要包含以下角色。
- 抽象構件(Component)角色:定義一個抽象接口以規範準備接收附加責任的對象。
- 具體構件(Concrete Component)角色:實現抽象構件,通過裝飾角色爲其添加一些職責。
- 抽象裝飾(Decorator)角色:繼承抽象構件,幷包含具體構件的實例,可以通過其子類擴展具體構件的功能。
- 具體裝飾(ConcreteDecorator)角色:實現抽象裝飾的相關方法,並給具體構件對象添加附加的責任。
裝飾模式的結構圖如圖 1 所示。
2.2 模式的實現
裝飾模式的實現代碼如下:
package decorator;
public class DecoratorPattern
{
public static void main(String[] args)
{
Component p=new ConcreteComponent();
p.operation();
System.out.println("---------------------------------");
Component d=new ConcreteDecorator(p);
d.operation();
}
}
//抽象構件角色
interface Component
{
public void operation();
}
//具體構件角色
class ConcreteComponent implements Component
{
public ConcreteComponent()
{
System.out.println("創建具體構件角色");
}
public void operation()
{
System.out.println("調用具體構件角色的方法operation()");
}
}
//抽象裝飾角色
class Decorator implements Component
{
private Component component;
public Decorator(Component component)
{
this.component=component;
}
public void operation()
{
component.operation();
}
}
//具體裝飾角色
class ConcreteDecorator extends Decorator
{
public ConcreteDecorator(Component component)
{
super(component);
}
public void operation()
{
super.operation();
addedFunction();
}
public void addedFunction()
{
System.out.println("爲具體構件角色增加額外的功能addedFunction()");
}
}
程序運行結果如下:
創建具體構件角色 調用具體構件角色的方法operation() --------------------------------- 調用具體構件角色的方法operation() 爲具體構件角色增加額外的功能addedFunction()
3. 裝飾模式的應用實例
【例1】用裝飾模式實現遊戲角色“莫莉卡·安斯蘭”的變身。
分析:在《惡魔戰士》中,遊戲角色“莫莉卡·安斯蘭”的原身是一個可愛少女,但當她變身時,會變成頭頂及背部延伸出蝙蝠狀飛翼的女妖,當然她還可以變爲穿着漂亮外衣的少女。這些都可用裝飾模式來實現,在本實例中的“莫莉卡”原身有 setImage(String t) 方法決定其顯示方式,而其 變身“蝙蝠狀女妖”和“着裝少女”可以用 setChanger() 方法來改變其外觀,原身與變身後的效果用 display() 方法來顯示(點此下載其原身和變身後的圖片),圖 2 所示是其結構圖。
程序代碼如下:
package decorator;
import java.awt.*;
import javax.swing.*;
public class MorriganAensland
{
public static void main(String[] args)
{
Morrigan m0=new original();
m0.display();
Morrigan m1=new Succubus(m0);
m1.display();
Morrigan m2=new Girl(m0);
m2.display();
}
}
//抽象構件角色:莫莉卡
interface Morrigan
{
public void display();
}
//具體構件角色:原身
class original extends JFrame implements Morrigan
{
private static final long serialVersionUID = 1L;
private String t="Morrigan0.jpg";
public original()
{
super("《惡魔戰士》中的莫莉卡·安斯蘭");
}
public void setImage(String t)
{
this.t=t;
}
public void display()
{
this.setLayout(new FlowLayout());
JLabel l1=new JLabel(new ImageIcon("src/decorator/"+t));
this.add(l1);
this.pack();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
}
//抽象裝飾角色:變形
class Changer implements Morrigan
{
Morrigan m;
public Changer(Morrigan m)
{
this.m=m;
}
public void display()
{
m.display();
}
}
//具體裝飾角色:女妖
class Succubus extends Changer
{
public Succubus(Morrigan m)
{
super(m);
}
public void display()
{
setChanger();
super.display();
}
public void setChanger()
{
((original) super.m).setImage("Morrigan1.jpg");
}
}
//具體裝飾角色:少女
class Girl extends Changer
{
public Girl(Morrigan m)
{
super(m);
}
public void display()
{
setChanger();
super.display();
}
public void setChanger()
{
((original) super.m).setImage("Morrigan2.jpg");
}
}
4. 裝飾模式的應用場景
前面講解了關於裝飾模式的結構與特點,下面介紹其適用的應用場景,裝飾模式通常在以下幾種情況使用。
- 當需要給一個現有類添加附加職責,而又不能採用生成子類的方法進行擴充時。例如,該類被隱藏或者該類是終極類或者採用繼承方式會產生大量的子類。
- 當需要通過對現有的一組基本功能進行排列組合而產生非常多的功能時,採用繼承關係很難實現,而採用裝飾模式卻很好實現。
- 當對象的功能要求可以動態地添加,也可以再動態地撤銷時。
裝飾模式在 Java 語言中的最著名的應用莫過於 Java I/O 標準庫的設計了。例如,InputStream 的子類 FilterInputStream,OutputStream 的子類 FilterOutputStream,Reader 的子類 BufferedReader 以及 FilterReader,還有 Writer 的子類 BufferedWriter、FilterWriter 以及 PrintWriter 等,它們都是抽象裝飾類。
下面代碼是爲 FileReader 增加緩衝區而採用的裝飾類 BufferedReader 的例子:
BufferedReader in=new BufferedReader(new FileReader("filename.txtn));
String s=in.readLine();
5. 裝飾模式的擴展
裝飾模式所包含的 4 個角色不是任何時候都要存在的,在有些應用環境下模式是可以簡化的,如以下兩種情況。
(1) 如果只有一個具體構件而沒有抽象構件時,可以讓抽象裝飾繼承具體構件,其結構圖如圖 4 所示。
(2) 如果只有一個具體裝飾時,可以將抽象裝飾和具體裝飾合併,其結構圖如圖 5 所示。