結構型模式概述
結構型模式概述
裝飾器模式(Decorator Pattern)允許向一個現有的對象添加新的功能,同時又不改變其結構。這種類型的設計模式屬於結構型模式,它是作爲現有的類的一個包裝。
這種模式創建了一個裝飾類,用來包裝原有的類,並在保持類方法簽名完整性的前提下,提供了額外的功能。
我們通過下面的實例來演示裝飾器模式的用法。其中,我們將把一個形狀裝飾上不同的顏色,同時又不改變形狀類。
模式動機及定義
模式動機
一般有兩種方式可以實現給一個類或對象增加行爲:
- 繼承機制,使用繼承機制是給現有類添加功能的一種有效途徑,通過繼承一個現有類可以使得子類在擁有自身方法的同時還擁有父類的方法。但是這種方法是靜態的,用戶不能控制增加行爲的方式和時機。
- 關聯機制,即將一個類的對象嵌入另一個對象中,由另一個對象來決定是否調用嵌入對象的行爲以便擴展自己的行爲,我們稱這個嵌入的對象爲裝飾器(Decorator)。
裝飾模式以對客戶透明的方式動態地給一個對象附加上更多的責任,換言之,客戶端並不會覺得對象在裝飾前和裝飾後有什麼不同。裝飾模式可以在不需要創造更多子類的情況下,將對象的功能加以擴展。這就是裝飾模式的模式動機。
模式定義
裝飾模式(Decorator Pattern) :動態地給一個對象增加一些額外的職責(Responsibility),就增加對象功能來說,裝飾模式比生成子類實現更爲靈活。其別名也可以稱爲包裝器(Wrapper),與適配器模式的別名相同,但它們適用於不同的場合。根據翻譯的不同,裝飾模式也有人稱之爲“油漆工模式”,它是一種對象結構型模式。
模式結構與分析
模式結構
裝飾模式包含如下角色:
- Component: 抽象構件
- ConcreteComponent: 具體構件
- Decorator: 抽象裝飾類
- ConcreteDecorator: 具體裝飾類
模式分析
與繼承關係相比,關聯關係的主要優勢在於不會破壞類的封裝性,而且繼承是一種耦合度較大的靜態關係,無法在程序運行時動態擴展。在軟件開發階段,關聯關係雖然不會比繼承關係減少編碼量,但是到了軟件維護階段,由於關聯關係使系統具有較好的松耦合性,因此使得系統更加容易維護。當然,關聯關係的缺點是比繼承關係要創建更多的對象。
使用裝飾模式來實現擴展比繼承更加靈活,它以對客戶透明的方式動態地給一個對象附加更多的責任。裝飾模式可以在不需要創造更多子類的情況下,將對象的功能加以擴展。
典型的抽象裝飾類代碼:
public class Decorator extends Component
{
private Component component;
public Decorator(Component component)
{
this.component=component;
}
public void operation()
{
component.operation();
}
}
典型的具體裝飾類代碼:
public class ConcreteDecorator extends Decorator
{
public ConcreteDecorator(Component component)
{
super(component);
}
public void operation() //在子類的operation方法中增加一個方法
{
super.operation();
addedBehavior();
}
public void addedBehavior()
{
//新增方法
}
}
模式實例與解析
模式實例與解析
實例一:變形金剛
變形金剛在變形之前是一輛汽車,它可以在陸地上移動。當它變成機器人之後除了能夠在陸地上移動之外,還可以說話;如果需要,它還可以變成飛機,除了在陸地上移動還可以在天空中飛翔。
public interface Transform
{
public void move();
}
//具體構建
public final class Car implements Transform
{
public Car()
{
System.out.println("變形金剛是一輛車!");
}
public void move()
{
System.out.println("在陸地上移動!");
}
}
//抽象裝飾類
public class Changer implements Transform
{
private Transform transform;
public Changer(Transform transform)
{
this.transform=transform;
}
public void move()
{
transform.move();
}
}
//具體裝飾類
public class Robot extends Changer
{
public Robot(Transform transform)
{
super(transform);
System.out.println("變成機器人!");
}
public void say()
{
System.out.println("說話!");
}
}
//具體裝飾類
public class Airplane extends Changer
{
public Airplane(Transform transform)
{
super(transform);
System.out.println("變成飛機!");
}
public void fly()
{
System.out.println("在天空飛翔!");
}
}
//客戶端
public class Client
{
public static void main(String args[])
{
Transform camaro;
camaro=new Car();
camaro.move();
System.out.println("-----------------------------");
Airplane bumblebee=new Airplane(camaro);
bumblebee.move();
bumblebee.fly();
}
}
實例二:多重加密系統
某系統提供了一個數據加密功能,可以對字符串進行加密。最簡單的加密算法通過對字母進行移位來實現,同時還提供了稍複雜的逆向輸出加密,還提供了更爲高級的求模加密。用戶先使用最簡單的加密算法對字符串進行加密,如果覺得還不夠可以對加密之後的結果使用其他加密算法進行二次加密,當然也可以進行第三次加密。現使用裝飾模式設計該多重加密系統。
//抽象構建
public interface Cipher
{
public String encrypt(String plainText);
}
//具體構建
public class SimpleCipher implements Cipher
{
public String encrypt(String plainText)
{
String str="";
for(int i=0;i<plainText.length();i++)
{
char c=plainText.charAt(i);
if(c>='a'&&c<='z')
{
c+=6;
if(c>'z') c-=26;
if(c<'a') c+=26;
}
if(c>='A'&&c<='Z')
{
c+=6;
if(c>'Z') c-=26;
if(c<'A') c+=26;
}
str+=c;
}
return str;
}
}
//抽象裝飾類
public class CipherDecorator implements Cipher
{
private Cipher cipher;
public CipherDecorator(Cipher cipher)
{
this.cipher=cipher;
}
public String encrypt(String plainText)
{
return cipher.encrypt(plainText);
}
}
//具體裝飾類
public class ComplexCipher extends CipherDecorator
{
public ComplexCipher(Cipher cipher)
{
super(cipher);
}
public String encrypt(String plainText)
{
String result=super.encrypt(plainText);
result=reverse(result);
return result;
}
public String reverse(String text)
{
String str="";
for(int i=text.length();i>0;i--)
{
str+=text.substring(i-1,i);
}
return str;
}
}
//具體裝飾類
public class AdvancedCipher extends CipherDecorator
{
public AdvancedCipher(Cipher cipher)
{
super(cipher);
}
public String encrypt(String plainText)
{
String result=super.encrypt(plainText);
result=mod(result);
return result;
}
public String mod(String text)
{
String str="";
for(int i=0;i<text.length();i++)
{
String c=String.valueOf(text.charAt(i)%6);
str+=c;
}
return str;
}
}
//客戶端
public class Client
{
public static void main(String args[])
{
String password="sunnyLiu"; //明文
String cpassword; //密文
Cipher sc,cc;
sc=new SimpleCipher();
cpassword=sc.encrypt(password);
System.out.println(cpassword);
System.out.println("---------------------");
cc=new ComplexCipher(sc);
cpassword=cc.encrypt(password);
System.out.println(cpassword);
System.out.println("---------------------");
/*
ac=new AdvancedCipher(cc);
cpassword=ac.encrypt(password);
System.out.println(cpassword);
System.out.println("---------------------");
*/
}
}
模式效果
裝飾器模式的優點
- 裝飾模式與繼承關係的目的都是要擴展對象的功能,但是裝飾模式可以提供比繼承更多的靈活性。
- 可以通過一種動態的方式來擴展一個對象的功能,通過配置文件可以在運行時選擇不同的裝飾器,從而實現不同的行爲。
- 通過使用不同的具體裝飾類以及這些裝飾類的排列組合,可以創造出很多不同行爲的組合。可以使用多個具體裝飾類來裝飾同一對象,得到功能更爲強大的對象。
- 具體構件類與具體裝飾類可以獨立變化,用戶可以根據需要增加新的具體構件類和具體裝飾類,在使用時再對其進行組合,原有代碼無須改變,符合“開閉原則”。
裝飾器模式的缺點
- 使用裝飾模式進行系統設計時將產生很多小對象,這些對象的區別在於它們之間相互連接的方式有所不同,而不是它們的類或者屬性值有所不同,同時還將產生很多具體裝飾類。這些裝飾類和小對象的產生將增加系統的複雜度,加大學習與理解的難度。
- 這種比繼承更加靈活機動的特性,也同時意味着裝飾模式比繼承更加易於出錯,排錯也很困難,對於多次裝飾的對象,調試時尋找錯誤可能需要逐級排查,較爲煩瑣。
適用環境和應用
適用環境
在以下情況下可以使用裝飾模式:
- 在不影響其他對象的情況下,以動態、透明的方式給單個對象添加職責。
- 需要動態地給一個對象增加功能,這些功能也可以動態地被撤銷。
- 當不能採用繼承的方式對系統進行擴充或者採用繼承不利於系統擴展和維護時。不能採用繼承的情況主要有兩類:第一類是系統中存在大量獨立的擴展,爲支持每一種組合將產生大量的子類,使得子類數目呈爆炸性增長;第二類是因爲類定義不能繼承(如final類)。
應用
- 在javax.swing包中,可以通過裝飾模式動態給一些構件增加新的行爲或改善其外觀顯示。
如JList構件本身並不支持直接滾動,即沒有滾動條,要創建可以滾動的列表,可以使用如下代碼實現:
JList list = new JList();
JScrollPane sp = new JScrollPane(list);
- 裝飾模式在JDK中最經典的實例是Java IO。
以InputStream爲例:
抽象裝飾類:FilterInputStream
……
protected volatile InputStream in;
protected FilterInputStream(InputStream in) {
this.in = in;
}
……
角色分配:
- 抽象構件類:InputStream
- 具體構件類:FileInputStream、ByteArrayInputStream等
- 抽象裝飾類:FilterInputStream
- 具體裝飾類:BufferedInputStream、DataInputStream等
客戶端使用:
FileInputStream inFS=new FileInputStream("temp/fileSrc.txt");
BufferedInputStream inBS=new BufferedInputStream(inFS);
//定義一個字節數組,用於存放緩衝數據
byte[] data = new byte[1024];
inBS.read(data);
模式擴展
裝飾模式的簡化-需要注意的問題
- 一個裝飾類的接口必須與被裝飾類的接口保持相同,對於客戶端來說無論是裝飾之前的對象還是裝飾之後的對象都可以一致對待。
- 儘量保持具體構件類Component作爲一個“輕”類,也就是說不要把太多的邏輯和狀態放在具體構件類中,可以通過裝飾類對其進行擴展。
- 如果只有一個具體構件類而沒有抽象構件類,那麼抽象裝飾類可以作爲具體構件類的直接子類。
裝飾模式的簡化
透明裝飾模式(多重加密系統)
在透明裝飾模式中,要求客戶端完全針對抽象編程,裝飾模式的透明性要求客戶端程序不應該聲明具體構件類型和具體裝飾類型,而應該全部聲明爲抽象構件類型。
Cipher sc,cc,ac;
sc=new SimpleCipher();
cc=new ComplexCipher(sc);
ac=new AdvancedCipher(cc);
半透明裝飾模式(變形金剛)
大多數裝飾模式都是半透明(semi-transparent)的裝飾模式,而不是完全透明(transparent)的。即允許用戶在客戶端聲明具體裝飾者類型的對象,調用在具體裝飾者中新增的方法。
Transform camaro;
camaro=new Car();
camaro.move();
Robot bumblebee=new Robot(camaro);
bumblebee.move();
bumblebee.say();