裝飾模式
定義
裝飾模式又名包裝(Wrapper)模式吹截。裝飾模式以對(duì)客戶(hù)端透明的方式擴(kuò)展對(duì)象的功能,是繼承關(guān)系的一個(gè)替代方案凝危。
在軟件開(kāi)發(fā)中波俄,往往會(huì)有這樣一種需求,我們需要在不改變?cè)到y(tǒng)代碼的時(shí)候蛾默,給一個(gè)類(lèi)增加一個(gè)新的功能或特性懦铺,而Java中單繼承的特性往往會(huì)限制我們對(duì)原代碼的拓展。采用裝飾模式可以使用非繼承的方式且不改變?cè)到y(tǒng)的情況下拓展新的功能/特性趴生。
UML圖
裝飾者模式是一種比較容易理解的設(shè)計(jì)模式阀趴。它的UML圖如下:
裝飾者模式有以下幾個(gè)角色:
- Component(抽象構(gòu)建):它是具體構(gòu)建與抽象裝飾類(lèi)的共同父類(lèi),定義一個(gè)統(tǒng)一的規(guī)范苍匆,使客戶(hù)端以一致的方法處理裝飾前后的對(duì)象刘急。
- ConcreteComponent(具體構(gòu)建):抽象構(gòu)建的具體實(shí)現(xiàn),需要被裝飾的對(duì)象浸踩。
- Decorator(抽象裝飾類(lèi)):它也是抽象構(gòu)建的字類(lèi)叔汁,它用作給具體構(gòu)建增加新的功能/特性,但是具體增加方法由它的字類(lèi)實(shí)現(xiàn)。它維持一個(gè)抽象構(gòu)建的引用据块,通過(guò)該引用調(diào)用未裝飾前具體構(gòu)建的方法码邻。
- ConcreteDecorator(具體裝飾類(lèi)):抽象裝飾類(lèi)的子類(lèi),實(shí)現(xiàn)向具體構(gòu)建新增功能/特性另假。
我覺(jué)得裝飾模式的核心就是 Decorator(抽象裝飾類(lèi)) 像屋,它通過(guò)一個(gè)持有一個(gè)抽象構(gòu)建的引用來(lái)實(shí)現(xiàn)對(duì)具體構(gòu)建的調(diào)用,且讓子類(lèi)可以新增方法特性。
代碼
//抽象構(gòu)建
public abstract class Component {
public abstract void operation();
}
//具體構(gòu)建
public class ConcreteComponent extends Component {
@Override
public void operation() {
System.out.println("ConcreteComponent operation");
}
}
//抽象裝飾類(lèi) 核心
public class Decorator extends Component {
Component component; //持有一個(gè)抽象構(gòu)建的引用
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
}
}
//具體的裝飾類(lèi)
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");
}
}
......
客戶(hù)端調(diào)用
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("----------------------------");
}
}
客戶(hù)端調(diào)用結(jié)果:
ConcreteComponent operation
----------------------------
ConcreteComponent operation
ConcreteDecoratorA methodA
----------------------------
ConcreteComponent operation
ConcreteDecoratorA methodA
ConcreteDecoratorB methodB
----------------------------
從上面可以看到,我在客戶(hù)端分別創(chuàng)建了三個(gè)Component的子類(lèi)齿椅,第一個(gè)是具體構(gòu)建類(lèi),第二個(gè)是具體裝飾類(lèi)A凌受,持有具體構(gòu)建類(lèi)的引用,第三個(gè)是具體裝飾類(lèi)B思杯,持有具體裝飾類(lèi)A的引用胜蛉,調(diào)用同一個(gè)方法后,可以從輸出結(jié)果中看到色乾,每一個(gè)裝飾類(lèi)都實(shí)現(xiàn)了被裝飾類(lèi)的方法同時(shí)可以裝飾自己的方法誊册。
實(shí)例
老規(guī)矩,還是用一個(gè)實(shí)例來(lái)演練一番杈湾。
某軟件公司欲開(kāi)發(fā)了一個(gè)數(shù)據(jù)加密模塊解虱,可以對(duì)字符串進(jìn)行加密。最簡(jiǎn)單的加密算法通過(guò)對(duì)字母進(jìn)行移位來(lái)實(shí)現(xiàn)漆撞,同時(shí)還提供了稍復(fù)雜的逆向輸出加密殴泰,還提供了更為高級(jí)的求模加密。用戶(hù)先使用最簡(jiǎn)單的加密算法對(duì)字符串進(jìn)行加密浮驳,如果覺(jué)得還不夠可以對(duì)加密之后的結(jié)果使用其他加密算法進(jìn)行二次加密悍汛,當(dāng)然也可以進(jìn)行第三次加密。試使用裝飾模式設(shè)計(jì)該多重加密系統(tǒng)至会。
分析需求离咐,字母位移加密是原始的加密方法,其他算法的二次三次加密都是對(duì)原加密算法對(duì)裝飾奉件。
UML圖
根據(jù)需求繪制UML圖如下
基本上都是套用定義的UML圖宵蛀,根據(jù)UML圖可以很清楚的看清整個(gè)軟件架構(gòu)。
代碼
// 抽象構(gòu)建
public abstract class EncryptComponent {
abstract String encrypt(String str);
}
//原始加密算法
public class OriginalEncrypt extends EncryptComponent {
@Override
String encrypt(String str) {
System.out.println("對(duì)字符串 \'"+str+"\' 使用原始加密 =====> 原始加密結(jié)果");
String encryptStr = "原始加密結(jié)果";
return encryptStr;
}
}
//抽象裝飾類(lèi)
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("對(duì)字符串 \'"+str+"\' 使用OtherA加密 =====> OtherA加密結(jié)果");
return "OtherA 加密結(jié)果";
}
}
//另一種加密算法B 代碼類(lèi)似 可以到我的git上去clone
...
客戶(hù)端測(cè)試
public class Client {
public static void main(String[] args) {
EncryptComponent originalEncrypt, otherAEncrypt, otherBEncrypt;
String result;
originalEncrypt = new OriginalEncrypt();
result = originalEncrypt.encrypt("初始數(shù)據(jù)");
System.out.println("-----------------------------------------------");
otherAEncrypt = new OtherAEncrypt(originalEncrypt);
result = otherAEncrypt.encrypt("初始數(shù)據(jù)");
System.out.println("-----------------------------------------------");
otherBEncrypt = new OtherBEncrypt(originalEncrypt);
result = otherBEncrypt.encrypt("初始數(shù)據(jù)");
System.out.println("-----------------------------------------------");
otherBEncrypt = new OtherBEncrypt(otherAEncrypt);
result = otherBEncrypt.encrypt("初始數(shù)據(jù)");
System.out.println("-----------------------------------------------");
}
}
結(jié)果
對(duì)字符串 '初始數(shù)據(jù)' 使用原始加密 =====> 原始加密結(jié)果
-----------------------------------------------
對(duì)字符串 '初始數(shù)據(jù)' 使用原始加密 =====> 原始加密結(jié)果
對(duì)字符串 '原始加密結(jié)果' 使用OtherA加密 =====> OtherA加密結(jié)果
-----------------------------------------------
對(duì)字符串 '初始數(shù)據(jù)' 使用原始加密 =====> 原始加密結(jié)果
對(duì)字符串 '原始加密結(jié)果' 使用OtherB加密 =====> OtherB加密結(jié)果
-----------------------------------------------
對(duì)字符串 '初始數(shù)據(jù)' 使用原始加密 =====> 原始加密結(jié)果
對(duì)字符串 '原始加密結(jié)果' 使用OtherA加密 =====> OtherA加密結(jié)果
對(duì)字符串 'OtherA 加密結(jié)果' 使用OtherB加密 =====> OtherB加密結(jié)果
-----------------------------------------------
相信大部分同學(xué)都可以猜到結(jié)果及邏輯處理過(guò)程县貌。
簡(jiǎn)化
其實(shí)當(dāng)系統(tǒng)中具體構(gòu)建只有一個(gè)的時(shí)候术陶,我們可以省略抽象構(gòu)建,讓具體構(gòu)建同時(shí)擔(dān)任這兩個(gè)角色煤痕,如下:
如圖梧宫,可以簡(jiǎn)化系統(tǒng)的復(fù)雜度接谨,去處冗余的代碼。具體代碼就不貼了塘匣,相信對(duì)于大家應(yīng)該不難理解脓豪。
半透明裝飾模式
通過(guò)上面的代碼,相信大部分同學(xué)都會(huì)感覺(jué)有點(diǎn)熟悉忌卤,這種一個(gè)類(lèi)包裹另一個(gè)類(lèi)的套路像不像JAVA 數(shù)據(jù)IO 操作扫夜。
FileInputStream fileInputStream = new FileInputStream("DecoratorPattern/test.txt");
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);
的確,JAVA的IO操作就是裝飾模式的一個(gè)具體實(shí)現(xiàn)驰徊,在JAVA中裝飾模式是使用十分頻繁的一種設(shè)計(jì)模式历谍。
但是,大家有沒(méi)有發(fā)現(xiàn)這里的裝飾模式與我們前面的例子有什么不同地方呢辣垒?
在上面的例子中,我們?cè)诳蛻?hù)端聲明實(shí)例時(shí)
EncryptComponent originalEncrypt, otherAEncrypt, otherBEncrypt;
所有的具體構(gòu)建或者具體裝飾都是以抽象構(gòu)建來(lái)定義印蔬,因?yàn)橥ㄟ^(guò)UML圖我們知道它們都是抽象構(gòu)建的子類(lèi)勋桶。這種對(duì)于客戶(hù)端是而言是完全針對(duì)抽象編程,也就是透明裝飾模式
但是所有的具體構(gòu)建或者具體裝飾都以抽象構(gòu)建來(lái)定義會(huì)導(dǎo)致一個(gè)問(wèn)題侥猬,它們都只能調(diào)用抽象構(gòu)建中定義的方法例驹,而在實(shí)際開(kāi)發(fā)中,我們往往需要單獨(dú)使用具體裝飾類(lèi)中的方法退唠,這個(gè)時(shí)候使用抽象構(gòu)建來(lái)定義具體裝飾類(lèi)就不合適了鹃锈。
當(dāng)我們需要單獨(dú)使用具體裝飾類(lèi)中的方法時(shí),我們就需要單獨(dú)以具體裝飾類(lèi)來(lái)定義聲明瞧预,這種就是半透明的裝飾模式屎债。
小結(jié)
裝飾模式降低了系統(tǒng)的耦合度,可以動(dòng)態(tài)增加或刪除對(duì)象的職責(zé)垢油,并使得需要裝飾的具體構(gòu)件類(lèi)和具體裝飾類(lèi)可以獨(dú)立變化盆驹,以便增加新的具體構(gòu)件類(lèi)和具體裝飾類(lèi)。對(duì)于拓展一個(gè)對(duì)象的功能滩愁,裝飾模式比繼承更加靈活躯喇。通過(guò)動(dòng)態(tài)的方式來(lái)拓展一個(gè)對(duì)象的功能,且可以進(jìn)行多次不同的裝飾硝枉。
在實(shí)際開(kāi)發(fā)中廉丽,透明裝飾模式的設(shè)計(jì)難度較大,而且使用不夠靈活妻味。而半透明裝飾模式可以給系統(tǒng)帶來(lái)更大的靈活性正压,且設(shè)計(jì)相對(duì)簡(jiǎn)單。