裝飾模式
定義
裝飾模式又名包裝(Wrapper)模式。裝飾模式以對客戶端透明的方式擴展對象的功能,是繼承關系的一個替代方案。
在軟件開發中,往往會有這樣一種需求,我們需要在不改變原系統代碼的時候,給一個類增加一個新的功能或特性,而Java中單繼承的特性往往會限制我們對原代碼的拓展。采用裝飾模式可以使用非繼承的方式且不改變原系統的情況下拓展新的功能/特性。
UML圖
裝飾者模式是一種比較容易理解的設計模式。它的UML圖如下:
裝飾者模式有以下幾個角色:
- Component(抽象構建):它是具體構建與抽象裝飾類的共同父類,定義一個統一的規范,使客戶端以一致的方法處理裝飾前后的對象。
- ConcreteComponent(具體構建):抽象構建的具體實現,需要被裝飾的對象。
- Decorator(抽象裝飾類):它也是抽象構建的字類,它用作給具體構建增加新的功能/特性,但是具體增加方法由它的字類實現。它維持一個抽象構建的引用,通過該引用調用未裝飾前具體構建的方法。
- ConcreteDecorator(具體裝飾類):抽象裝飾類的子類,實現向具體構建新增功能/特性。
我覺得裝飾模式的核心就是 Decorator(抽象裝飾類) ,它通過一個持有一個抽象構建的引用來實現對具體構建的調用,且讓子類可以新增方法特性。
代碼
//抽象構建
public abstract class Component {
public abstract void operation();
}
//具體構建
public class ConcreteComponent extends Component {
@Override
public void operation() {
System.out.println("ConcreteComponent operation");
}
}
//抽象裝飾類 核心
public class Decorator extends Component {
Component component; //持有一個抽象構建的引用
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
}
}
//具體的裝飾類
public class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
methodA();
}
public void methodA(){
System.out.println("ConcreteDecoratorA methodA");
}
}
......
客戶端調用
public class Client {
public static void main(String[] args) {
Component concreteComponent, concreteDecoratorA, concreteDecoratorB;
concreteComponent = new ConcreteComponent();
concreteComponent.operation();
System.out.println("----------------------------");
concreteDecoratorA = new ConcreteDecoratorA(concreteComponent);
concreteDecoratorA.operation();
System.out.println("----------------------------");
concreteDecoratorB = new ConcreteDecoratorB(concreteDecoratorA);
concreteDecoratorB.operation();
System.out.println("----------------------------");
}
}
客戶端調用結果:
ConcreteComponent operation
----------------------------
ConcreteComponent operation
ConcreteDecoratorA methodA
----------------------------
ConcreteComponent operation
ConcreteDecoratorA methodA
ConcreteDecoratorB methodB
----------------------------
從上面可以看到,我在客戶端分別創建了三個Component的子類,第一個是具體構建類,第二個是具體裝飾類A,持有具體構建類的引用,第三個是具體裝飾類B,持有具體裝飾類A的引用,調用同一個方法后,可以從輸出結果中看到,每一個裝飾類都實現了被裝飾類的方法同時可以裝飾自己的方法。
實例
老規矩,還是用一個實例來演練一番。
某軟件公司欲開發了一個數據加密模塊,可以對字符串進行加密。最簡單的加密算法通過對字母進行移位來實現,同時還提供了稍復雜的逆向輸出加密,還提供了更為高級的求模加密。用戶先使用最簡單的加密算法對字符串進行加密,如果覺得還不夠可以對加密之后的結果使用其他加密算法進行二次加密,當然也可以進行第三次加密。試使用裝飾模式設計該多重加密系統。
分析需求,字母位移加密是原始的加密方法,其他算法的二次三次加密都是對原加密算法對裝飾。
UML圖
根據需求繪制UML圖如下
基本上都是套用定義的UML圖,根據UML圖可以很清楚的看清整個軟件架構。
代碼
// 抽象構建
public abstract class EncryptComponent {
abstract String encrypt(String str);
}
//原始加密算法
public class OriginalEncrypt extends EncryptComponent {
@Override
String encrypt(String str) {
System.out.println("對字符串 \'"+str+"\' 使用原始加密 =====> 原始加密結果");
String encryptStr = "原始加密結果";
return encryptStr;
}
}
//抽象裝飾類
public class EncryptDecorator extends EncryptComponent {
EncryptComponent encryptComponent;
public EncryptDecorator(EncryptComponent encryptComponent) {
this.encryptComponent = encryptComponent;
}
@Override
String encrypt(String str) {
return encryptComponent.encrypt(str);
}
}
//另一種加密算法A
public class OtherAEncrypt extends EncryptDecorator {
public OtherAEncrypt(EncryptComponent encryptComponent) {
super(encryptComponent);
}
@Override
String encrypt(String str) {
return otherAEncrypt(super.encrypt(str));
}
public String otherAEncrypt(String str){
System.out.println("對字符串 \'"+str+"\' 使用OtherA加密 =====> OtherA加密結果");
return "OtherA 加密結果";
}
}
//另一種加密算法B 代碼類似 可以到我的git上去clone
...
客戶端測試
public class Client {
public static void main(String[] args) {
EncryptComponent originalEncrypt, otherAEncrypt, otherBEncrypt;
String result;
originalEncrypt = new OriginalEncrypt();
result = originalEncrypt.encrypt("初始數據");
System.out.println("-----------------------------------------------");
otherAEncrypt = new OtherAEncrypt(originalEncrypt);
result = otherAEncrypt.encrypt("初始數據");
System.out.println("-----------------------------------------------");
otherBEncrypt = new OtherBEncrypt(originalEncrypt);
result = otherBEncrypt.encrypt("初始數據");
System.out.println("-----------------------------------------------");
otherBEncrypt = new OtherBEncrypt(otherAEncrypt);
result = otherBEncrypt.encrypt("初始數據");
System.out.println("-----------------------------------------------");
}
}
結果
對字符串 '初始數據' 使用原始加密 =====> 原始加密結果
-----------------------------------------------
對字符串 '初始數據' 使用原始加密 =====> 原始加密結果
對字符串 '原始加密結果' 使用OtherA加密 =====> OtherA加密結果
-----------------------------------------------
對字符串 '初始數據' 使用原始加密 =====> 原始加密結果
對字符串 '原始加密結果' 使用OtherB加密 =====> OtherB加密結果
-----------------------------------------------
對字符串 '初始數據' 使用原始加密 =====> 原始加密結果
對字符串 '原始加密結果' 使用OtherA加密 =====> OtherA加密結果
對字符串 'OtherA 加密結果' 使用OtherB加密 =====> OtherB加密結果
-----------------------------------------------
相信大部分同學都可以猜到結果及邏輯處理過程。
簡化
其實當系統中具體構建只有一個的時候,我們可以省略抽象構建,讓具體構建同時擔任這兩個角色,如下:
如圖,可以簡化系統的復雜度,去處冗余的代碼。具體代碼就不貼了,相信對于大家應該不難理解。
半透明裝飾模式
通過上面的代碼,相信大部分同學都會感覺有點熟悉,這種一個類包裹另一個類的套路像不像JAVA 數據IO 操作。
FileInputStream fileInputStream = new FileInputStream("DecoratorPattern/test.txt");
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);
的確,JAVA的IO操作就是裝飾模式的一個具體實現,在JAVA中裝飾模式是使用十分頻繁的一種設計模式。
但是,大家有沒有發現這里的裝飾模式與我們前面的例子有什么不同地方呢?
在上面的例子中,我們在客戶端聲明實例時
EncryptComponent originalEncrypt, otherAEncrypt, otherBEncrypt;
所有的具體構建或者具體裝飾都是以抽象構建來定義,因為通過UML圖我們知道它們都是抽象構建的子類。這種對于客戶端是而言是完全針對抽象編程,也就是透明裝飾模式
但是所有的具體構建或者具體裝飾都以抽象構建來定義會導致一個問題,它們都只能調用抽象構建中定義的方法,而在實際開發中,我們往往需要單獨使用具體裝飾類中的方法,這個時候使用抽象構建來定義具體裝飾類就不合適了。
當我們需要單獨使用具體裝飾類中的方法時,我們就需要單獨以具體裝飾類來定義聲明,這種就是半透明的裝飾模式。
小結
裝飾模式降低了系統的耦合度,可以動態增加或刪除對象的職責,并使得需要裝飾的具體構件類和具體裝飾類可以獨立變化,以便增加新的具體構件類和具體裝飾類。對于拓展一個對象的功能,裝飾模式比繼承更加靈活。通過動態的方式來拓展一個對象的功能,且可以進行多次不同的裝飾。
在實際開發中,透明裝飾模式的設計難度較大,而且使用不夠靈活。而半透明裝飾模式可以給系統帶來更大的靈活性,且設計相對簡單。