[把你的理性思維慢慢變成條件反射]
本文,我們講介紹裝飾模式,文章主題結構與上位一致。慣例,先來看看我們示例工程的環境:
操作系統:win7 x64
其他軟件:eclipse mars,jdk7
-------------------------------------------------------------------------------------------------------------------------------------經典問題:
人們的服裝搭配問題、家庭裝修問題、客戶定製化的功能問題等。本文以“人們的服裝搭配問題”爲例進行說明。
思路分析:
要點一:以人爲核心。
要點二:服裝搭配不改變核心。
要點三:搭配按需變化,具有不確定性。
示例工程:
錯誤寫法(1):
創建Person.java文件,具體內容如下:
package com.csdn.ingo.gof_Decorator.base;
public class Person {
private String name;
public Person(String name){
this.name = name;
}
public void wearTShirts(){
System.out.println("大T恤");
}
public void wearBigTrouse(){
System.out.println("垮褲");
}
public void wearSneakers(){
System.out.println("球鞋");
}
public void wearSuit(){
System.out.println("西裝");
}
public void wearTie(){
System.out.println("領帶");
}
public void wearLeatherShoes(){
System.out.println("皮鞋");
}
public void Show(){
System.out.println("裝扮後的"+name);
}
}
創建Window.java文件,具體內容如下:
package com.csdn.ingo.gof_Decorator.base;
public class Window {
public static void main(String[] args) {
Person ingo = new Person("Ingo");
System.out.println("第2種裝扮:");
ingo.wearTShirts();
ingo.wearBigTrouse();
ingo.wearSneakers();
ingo.Show();
System.out.println("第2種裝扮:");
ingo.wearSuit();
ingo.wearTie();
ingo.wearLeatherShoes();
ingo.Show();
}
}
錯誤原因:
雖然代碼工程正確,但違反了我們前文介紹的“單一職責原則”:Person的創建與裝飾功能混合在一起。違反“開閉原則”:由於裝飾的功能經常處於變動狀態,因此,對於某個功能的修改可能擴展到其他裝飾功能當中。故,base包下的這種寫法是錯誤的。
錯誤寫法(2):
創建Finery.java文件,具體內容如下:
package com.csdn.ingo.gof_Decorator.one;
public abstract class Finery {
public abstract void show();
}
創建BigTrouser.java,LeatherShoes.java,Sneakers.java,Suit.java,Tie.java,TShirts.java文件,在此僅列舉BigTrouser.java文件,具體內容如下:
package com.csdn.ingo.gof_Decorator.one;
public class BIgTrouser extends Finery {
@Override
public void show() {
System.out.println("垮褲");
}
}
創建Person.java文件,具體內容如下:
package com.csdn.ingo.gof_Decorator.one;
public class Person {
private String name;
public Person(String name){
this.name = name;
}
public void Show(){
System.out.println("裝扮的"+name);
}
}
創建Window.java,具體內容如下:
package com.csdn.ingo.gof_Decorator.one;
public class Window {
public static void main(String[] args) {
Person ingo = new Person("Ingo");
System.out.println("第1種裝扮:");
Finery tshirts = new TShirts();
Finery bigt = new BIgTrouser();
Finery sneaker = new Sneakers();
tshirts.show();
bigt.show();
sneaker.show();
ingo.Show();
System.out.println("第2種裝扮:");
Finery suit = new Suit();
Finery tie = new Tie();
Finery lea = new LeatherShoes();
suit.show();
tie.show();
lea.show();
ingo.Show();
}
}
錯誤原因:
這種寫法將裝飾的內容一個一個的都列舉了出來,這種做法將導致客戶端產生非常多的對象聲明,方法調用等,正如上文展示的那樣。並且導致後期維護成本的急劇上升。因此,這種採用繼承方式的設計也是錯誤的。
推薦寫法:
創建Finery.java文件,具體內容如下:
package com.csdn.ingo.gof_Decorator.three;
public class Finery extends Person {
protected Person component;
public void Decorate(Person component){
this.component = component;
}
@Override
public void show(){
if(component!=null){
component.show();
}
}
}
創建BigTrouser.java,LeatherShoes.java,Sneakers.java,Suit.java,Tie.java,TShirts.java文件,在此僅列舉BigTrouser.java文件,具體內容如下:
package com.csdn.ingo.gof_Decorator.three;
public class BIgTrouser extends Finery {
@Override
public void show() {
System.out.println("垮褲");
super.show();
}
}
創建Window.java,具體內容如下:
package com.csdn.ingo.gof_Decorator.three;
public class Window {
public static void main(String[] args) {
Person ingo = new Person("Ingo");
System.out.println("第1種裝扮:");
TShirts tshirts = new TShirts();
BIgTrouser bigt = new BIgTrouser();
Sneakers sneaker = new Sneakers();
tshirts.Decorate(ingo);
bigt.Decorate(tshirts);
sneaker.Decorate(bigt);
sneaker.show();
System.out.println("第2種裝扮:");
Suit suit = new Suit();
Tie tie = new Tie();
LeatherShoes lea = new LeatherShoes();
suit.Decorate(ingo);
tie.Decorate(suit);
lea.Decorate(tie);
lea.show();
}
}
推薦原因:
- 符合“單一職責原則”,“開閉原則”等
- 每個裝飾功能單元均是獨立的,達到易擴展,易維護的目的。
- 對客戶端而言,當擴展新的功能節點時,可以方便的增加相關功能。並且減少了測試的成本。
模式總結:
本例UML結構圖:
裝飾模式標準UML圖
概念總結:
裝飾模式:動態地給一個對象添加一些額外的職責,就增加功能來說,裝飾模式比生成子類更爲靈活。
組成部分:Component(抽象部件),ConcreteComponent(具體部件),Decorator(抽象裝飾),ConcreteDecorator(具體裝飾)四部分組成。
特別提醒:
- 具體構件類和裝飾類都實現了相同的抽象構件接口,因此裝飾模式以對客戶透明的方式動態地給一個對象附加上更多的功能
- 結構中所有對象均實現Operation()方法。
- Decorator中維護了一個Component的引用。
模板代碼:
Component.java文件:
package com.csdn.ingo.gof_Decorator.two;
public abstract class Component {
public abstract void Operation();
}
ConcreteComponent.java文件:
package com.csdn.ingo.gof_Decorator.two;
public class ConcreteComponent extends Component {
@Override
public void Operation() {
System.out.println("具體對象的操作");
}
}
ConcreteDecoratorA.java文件:
package com.csdn.ingo.gof_Decorator.two;
public class ConcreteDecoratorA extends Decorator {
private String addedState;//本類獨有,以區別於B
@Override
public void Operation() {
super.Operation();
addedState = "New State";
System.out.println("具體裝飾對象A的操作");
}
}
package com.csdn.ingo.gof_Decorator.two;
public class ConcreteDecoratorB extends Decorator {
@Override
public void Operation() {
super.Operation();
AddedBehavior();
System.out.println("具體裝飾對象A的操作");
}
private void AddedBehavior() { //本類獨有,以區別於A
}
}
Decorator.java文件:
package com.csdn.ingo.gof_Decorator.two;
public abstract class Decorator extends Component {
protected Component component;
public void setComponent(Component component){
this.component = component;
}
@Override
public void Operation() {
if(component!=null){
component.Operation();//調用原有業務操作
}
}
}
在抽象裝飾類Decorator中定義了一個Component類型的對象component,維持一個對抽象構件對象的引用,並可以通過構造方法或Setter方法將一個Component類型的對象注入進來,同時由於Decorator類實現了抽象構件Component接口,因此需要實現在其中聲明的業務方法operation(),需要注意的是在Decorator中並未真正實現operation()方法,而只是調用原有component對象的operation()方法,它沒有真正實施裝飾,而是提供一個統一的接口,將具體裝飾過程交給子類完成。
package com.csdn.ingo.gof_Decorator.two;
public class Window {
public static void main(String[] args) {
ConcreteComponent c = new ConcreteComponent();
ConcreteDecoratorA d1 = new ConcreteDecoratorA();
ConcreteDecoratorB d2 = new ConcreteDecoratorB();
d1.setComponent(c);
d2.setComponent(d1);
d2.Operation();
}
}
問題:爲什麼模板代碼與示例代碼結構不同?
答:當只有一個ConcreteComponent類,而沒有抽象的Component類時,那麼Decorator類可以是ConcreteComponent的一個子類。同理,如果只有一個ConcreteDecorator類,那麼就沒有必要建立一個獨立的Decorator類,而可以把Decorator和ConcreteDecorator合併爲一個類。因此,在本例中服飾類直接作爲人的子類。
擴展問題:“交通工具也是隱含裝飾品”
在上文的例子“服裝搭配”中,所有的具體子類都是需要最終顯示出來的。但是,有些“隱含的裝飾品”僅僅存在在裝飾的過程當中,最終結果並不會體現出來,如本文的,ingo通過一系列的裝飾之後,乘坐各種交通工具前往約定地點的過程,就僅僅在“過程中”出現,到達之後就會消失。換句話說,某些操作僅僅存在過程中,並且,該操作與其他裝飾步驟無任何調用耦合關係。類似的場景還有,單據的審批過程,在最終的審批單中並不會顯示出審批過程一樣。等等類似的場景。
擴展問題的UML結構:
解決步驟:
對於“隱含的裝飾品”需要使用ConcreteDecorator來進行。即,客戶端需要顯示聲明一個具體的“交通工具”對象來進行裝飾,之後再將對象交還給裝飾流程。如此的好處是給系統設計帶來了更多的靈活性。
特別注意:
這裏的“過程中存在”的概念,即過程只有一次,該裝飾過程也只能發生一次,不可多次調用。
反思:
應用場景:
在不影響其他對象的條件下,爲已有功能動態地添加更多的功能,這些功能通常仍以原有的類作爲核心職責或主要行爲。
當不能使用繼承的方式作爲擴展,或者,不適合使用繼承的方式進行擴展與維護時。(原因:繼承會產生大量的子類,由此產生管理問題。另外,某些類不能夠內繼承擴展)
優點:
- 對當前已有功能的進行擴展是,靈活性更好,不會產生更多的子類。
- 可以通過動態的方式進行擴展,即使用配置文件,可以在運行時選擇不同的裝飾類。
- 裝飾的順序可以自由組合,以產生更多功能的對象。
- 分離核心功能與裝飾功能,達到“單一職責原則”的要求。
- 具體部件與具體裝飾可以獨立變化。新的修改不會影響原有類。
缺點:
- 客戶端可能會產生很多小對象。可能會佔用更多的資源,而影響性能。
- 多種排列組合的靈活性,可能會導致產生不符合要求的對象。
-------------------------------------------------------------------------------------------------------------------------------------
至此,被說了很多遍的設計模式---裝飾模式 結束
參考資料:
圖書:《大話設計模式》
其他博文:http://blog.csdn.NET/lovelion/article/details/7563445