基礎介紹:
動態地給一個物件新增一些額外的職責。適用於需要擴充套件一個類的功能,或給一個類新增多個變化的情況。
裝飾器,顧名思義就是在原有基礎上新增一些功能。
大家都只知道如果想單純的給原有類增加一些功能,可以直接繼續該類生成一個子類就可以。
舉個例子,如果現在有個手機類,想給手機貼膜,傳統的做法就是新建一個手機類的子類(手機貼膜子類),繼承自手機類。
使用這個子類就可以完成對手機的貼膜操作。
那如果又想給手機按保護殼的話,傳統做法有兩種,可以繼續新建一個手機類的子類(手機保護殼子類),繼承自手機類。
使用這個子類可以給手機按保護殼,但也就失去了給手機貼膜的功能。另一種做法,新建一個手機貼膜類的子類(手機貼膜+保護殼),也就是手機類的子子類。
這樣即可以貼膜也可以按手機殼。
大家思考一個問題,如果有很多個裝飾並且想隨意組合的話,那就有N個子類並且存在很深的繼承鏈路。
想要解決這個問題,就可以用到裝飾器了。
比如貼膜裝飾、保護殼裝飾、貼紙裝飾等等,它們都是獨立存在的,只繼承自裝飾器類。
什麼意思呢?就是說給手機貼膜的時候它並不會給手機按保護殼的功能,職責單一,貼膜裝飾器只負責給手機貼膜。
這樣做有什麼好處呢?好處就是這些裝飾可以隨意組合,比如即想貼膜又想按保護殼,就可以將貼膜裝飾+保護殼裝飾進組組合。
在裝飾器模式中各個角色有:
- 抽象構件(Component)角色:規範手機的構成
- 具體構件(ConcreteComponent)角色:繼承自抽象構件,具體實現手機。(大多情況下,可以省略抽象構件,抽象裝飾類可以直接繼承)
- 抽象裝飾類(Decorator)角色:建立一個構件(Component)物件的例項,可以使用這個例項呼叫原構件的功能,並規範裝飾類。
- 具體裝飾類(ConcreteDecorator)角色:繼承自抽象裝飾類,具體實現該裝飾功能。
應用場景:
原有類無法修改或者修改困難的情況下,對類進行多次擴充套件或功能性比較相互獨立,有效防止多次擴充套件的情況下子類的膨脹。
注:如果類的擴充套件比較簡單,並且不會多次進行擴充套件的情況下直接使用類的繼承生成子類的方式更為方便快捷。
建立方式:
為了方便說明,以下例項就不建立抽象構件了。
-
首先先看下不使用裝飾器進行類的擴充套件
1 /// <summary> 2 /// 手機類 3 /// </summary> 4 public class Phone 5 { 6 public void Print() 7 { 8 Console.WriteLine("手機"); 9 } 10 } 11 12 /// <summary> 13 /// 手機貼膜 14 /// </summary> 15 public class Sticker : Phone 16 { 17 public Sticker() 18 { 19 base.Print(); 20 } 21 22 /// <summary> 23 /// 進行貼膜 24 /// </summary> 25 public void AddSticker() 26 { 27 Console.WriteLine("給手機貼膜"); 28 } 29 } 30 31 /// <summary> 32 /// 手機保護殼 33 /// </summary> 34 public class ProtectiveCase : Phone 35 { 36 public ProtectiveCase() 37 { 38 base.Print(); 39 } 40 41 /// <summary> 42 /// 按保護殼 43 /// </summary> 44 public void AddProtectiveCase() 45 { 46 Console.WriteLine("給手機按保護殼"); 47 } 48 } 49 50 /// <summary> 51 /// 即貼膜又按保護殼 52 /// </summary> 53 public class ProtectiveCaseAndSticker : Sticker 54 { 55 public ProtectiveCaseAndSticker() 56 { 57 } 58 59 /// <summary> 60 /// 按保護殼 61 /// </summary> 62 public void AddProtectiveCase() 63 { 64 Console.WriteLine("給手機按保護殼"); 65 } 66 } 67 68 /// <summary> 69 /// 客戶端 70 /// </summary> 71 class Client 72 { 73 static void Main(string[] args) 74 { 75 //建立一個手機 76 Phone phone = new Phone(); 77 phone.Print(); 78 Console.WriteLine("\r\n"); 79 80 //給手機貼膜 81 Sticker sticker = new Sticker(); 82 sticker.AddSticker(); 83 Console.WriteLine("\r\n"); 84 85 //給手機按保護殼 86 ProtectiveCase protectiveCase = new ProtectiveCase(); 87 protectiveCase.AddProtectiveCase(); 88 Console.WriteLine("\r\n"); 89 90 //即貼膜又按保護殼 91 ProtectiveCaseAndSticker protectiveCaseAndSticker = new ProtectiveCaseAndSticker(); 92 protectiveCaseAndSticker.AddSticker(); 93 protectiveCaseAndSticker.AddProtectiveCase(); 94 Console.ReadKey(); 95 } 96 }
透過上述例項可以看出,如果各個擴充套件功能比較獨立的話可以直接進行繼承擴充套件。
如果各個功能直接有交集的情況下,會造成很深的繼承關係。
比如上述例項中,如果單獨貼膜或者單獨安裝保護殼則直接繼承手機類即可。
但如果想要即貼膜又要安裝保護殼,各自繼承手機類的方式就行不通了,只能在貼膜類或者保護殼類的基礎上進行擴充套件。
如果還有新增手機掛飾,那就還需要再一層繼承關係。
要解決這個問題就用到了裝飾器,下面看看使用裝飾器是怎麼給手機新增新功能的。
-
使用裝飾器模式對類進行擴充套件
1 /// <summary> 2 /// 手機類 3 /// </summary> 4 public class Phone 5 { 6 public void Print() 7 { 8 Console.WriteLine("手機"); 9 } 10 } 11 12 /// <summary> 13 /// 裝飾抽象類 14 /// </summary> 15 public abstract class Decorator : Phone 16 { 17 private Phone phone; 18 19 public Decorator(Phone p) 20 { 21 this.phone = p; 22 } 23 24 public abstract void AddDecorator(); 25 } 26 27 /// <summary> 28 /// 貼膜裝飾 29 /// </summary> 30 public class Sticker : Decorator 31 { 32 public Sticker(Phone p) 33 : base(p) 34 { 35 } 36 37 public override void AddDecorator() 38 { 39 Console.WriteLine("給手機貼膜"); 40 } 41 } 42 43 /// <summary> 44 /// 保護殼裝飾 45 /// </summary> 46 public class ProtectiveCase : Decorator 47 { 48 public ProtectiveCase(Phone p) 49 : base(p) 50 { 51 } 52 53 /// <summary> 54 /// 按保護殼 55 /// </summary> 56 public override void AddDecorator() 57 { 58 Console.WriteLine("給手機按保護殼"); 59 } 60 } 61 62 /// <summary> 63 /// 客戶端 64 /// </summary> 65 class Client 66 { 67 static void Main(string[] args) 68 { 69 //單獨給手機貼膜 70 Phone phone = new Phone(); 71 phone.Print(); 72 Decorator sticker = new Sticker(phone); 73 sticker.AddDecorator(); 74 75 //單獨給手機按保護殼 76 phone = new Phone(); 77 phone.Print(); 78 Decorator protectiveCase = new ProtectiveCase(phone); 79 protectiveCase.AddDecorator(); 80 Console.WriteLine("\r\n"); 81 82 //即貼膜又按保護殼 83 phone = new Phone(); 84 phone.Print(); 85 //首先建立貼膜裝飾例項,將手機物件傳入 86 Decorator decorator = new Sticker(phone); 87 //進行貼膜操作 88 decorator.AddDecorator(); 89 //建立保護殼裝飾例項,將貼膜後的手機物件傳入 90 decorator = new ProtectiveCase(decorator); 91 //進行按保護殼操作 92 decorator.AddDecorator(); 93 Console.ReadKey(); 94 } 95 }
從上述例項中可以看出,各個裝飾類只對裝飾抽象類負責,職責單一。
各個裝飾進行組合時,方便隨意。新增裝飾時,只需要新增一個繼承自裝飾抽象類的子類即可實現以原有裝飾的隨意組合使用。
總結:
想要擴充套件一個類的時候,傳統的繼承生成子類的形式,適用於擴充套件簡單,並且不會多次擴充套件的情況下。
而如果一個類的擴充套件是週期性,多次擴充套件的或功能性比較相互獨立的情況下,可以使用裝飾器,可以有效的解決傳統繼承擴充套件子類膨脹的問題。
裝飾模式是繼承的一個替代模式,裝飾模式可以動態擴充套件一個實現類的功能。