本系列將和大家分享面向對象23種設計模式中常用的幾種設計模式,本章主要簡單介紹下結構型設計模式。 ...
結構型設計模式:關註類與類之間的關係。結構型設計模式的核心:組合優於繼承(橫向關係優於縱向關係)。
縱向關係:繼承≈≈實現 超強關聯
橫向關係:>組合>聚合>關聯>依賴
依賴是出現在方法內部(在類A的方法內部對類B進行聲明和初始化)
另外三個是用語義區分的,可能都是一個屬性
Person類 有個大腦Header屬性 組合(同生共死)
Person類 有個手/腳屬性 聚合(成人)
Person類 有個iPhone屬性 關聯(非必須)
多種結構型設計模式其實都是用組合包一層,然後加功能,解決不同的問題,然後有不同的側重點,也有不同的規範。
結構型設計模式(7個):適配器模式、代理模式、裝飾器模式、外觀模式、組合模式、橋接模式、享元模式。
下麵我們結合幾種具體場景來分析幾種結構性設計模式。
1、適配器模式(Adapter Pattern)
解決重構的問題,新東西和舊系統不吻合,通過繼承/組合進行適配。
場景:程式中已經確定好數據訪問規範IDBHelper,我們的MySqlHelper、SqlServerHelper和OracleHelper都遵循了這個規範。隨著業務的變化,某一天我們的資料庫可能需要遷移到MongoDB,而它的數據訪問使用的是第三方的MongoDBHelper,其數據訪問規範和我們現有的IDBHelper規範不一致(沒有實現IDBHelper介面),但是呢我們又無法修改第三方的代碼。這時候我們就需要做相應的適配了。
程式原有代碼:
using System; namespace AdapterPattern { /// <summary> /// 數據訪問介面 /// </summary> public interface IDBHelper { void Add<T>(); void Delete<T>(); void Update<T>(); void Query<T>(); } }
using System; namespace AdapterPattern { /// <summary> /// MySql數據訪問 /// </summary> public class MySqlHelper : IDBHelper { public void Add<T>() { Console.WriteLine("This is {0} Add", this.GetType().Name); } public void Delete<T>() { Console.WriteLine("This is {0} Delete", this.GetType().Name); } public void Update<T>() { Console.WriteLine("This is {0} Update", this.GetType().Name); } public void Query<T>() { Console.WriteLine("This is {0} Query", this.GetType().Name); } } }
using System; namespace AdapterPattern { /// <summary> /// SqlServer數據訪問 /// </summary> public class SqlServerHelper : IDBHelper { public void Add<T>() { Console.WriteLine("This is {0} Add", this.GetType().Name); } public void Delete<T>() { Console.WriteLine("This is {0} Delete", this.GetType().Name); } public void Update<T>() { Console.WriteLine("This is {0} Update", this.GetType().Name); } public void Query<T>() { Console.WriteLine("This is {0} Query", this.GetType().Name); } } }
using System; namespace AdapterPattern { /// <summary> /// Oracle數據訪問 /// </summary> public class OracleHelper : IDBHelper { public void Add<T>() { Console.WriteLine("This is {0} Add", this.GetType().Name); } public void Delete<T>() { Console.WriteLine("This is {0} Delete", this.GetType().Name); } public void Update<T>() { Console.WriteLine("This is {0} Update", this.GetType().Name); } public void Query<T>() { Console.WriteLine("This is {0} Query", this.GetType().Name); } } }
第三方的MongoDBHelper
using System; namespace AdapterPattern { /// <summary> /// MongoDB數據訪問 /// </summary> public class MongoDBHelper { public MongoDBHelper() { Console.WriteLine($"構造MongoDBHelper"); } public void AddMongoDB<T>() { Console.WriteLine("This is {0} Add", this.GetType().Name); } public void DeleteMongoDB<T>() { Console.WriteLine("This is {0} Delete", this.GetType().Name); } public void UpdateMongoDB<T>() { Console.WriteLine("This is {0} Update", this.GetType().Name); } public void QueryMongoDB<T>() { Console.WriteLine("This is {0} Query", this.GetType().Name); } } }
可以看出程式原有的規範和第三方的規範不一致,那我們如何去做適配呢?
方法1:類適配器模式
繼承:既滿足現有的規範調用,又沒有修改MongoDBHelper。
using System; namespace AdapterPattern { /// <summary> /// 類適配器模式 /// 繼承 既滿足現有的規範調用,又沒有修改MongoDBHelper /// </summary> public class MongoDBHelperInherit : MongoDBHelper, IDBHelper { public MongoDBHelperInherit() { Console.WriteLine($"構造{this.GetType().Name}"); } public void Add<T>() { base.AddMongoDB<T>(); } public void Delete<T>() { base.DeleteMongoDB<T>(); } public void Update<T>() { base.UpdateMongoDB<T>(); } public void Query<T>() { base.QueryMongoDB<T>(); } } }
方法2:對象適配器模式
組合:既滿足現有的規範調用,又沒有修改MongoDBHelper。
using System; namespace AdapterPattern { /// <summary> /// 對象適配器模式 /// 組合 既滿足現有的規範調用,又沒有修改MongoDBHelper /// </summary> public class MongoDBHelperCompose : IDBHelper { private MongoDBHelper _mongoDBHelper; private readonly MongoDBHelper _mongoDBHelper1 = new MongoDBHelper(); //1、屬性註入 聲明寫死 一定有 /// <summary> /// 2、構造函數註入 /// 可以替換(需要抽象) _mongoDBHelper一定有值(不考慮其他構造函數) /// </summary> public MongoDBHelperCompose(MongoDBHelper mongoDBHelper) { _mongoDBHelper = mongoDBHelper; Console.WriteLine($"構造{this.GetType().Name}"); } /// <summary> /// 3、方法註入 可以替換(需要抽象) _mongoDBHelper不一定有值 /// </summary> public void SetObject(MongoDBHelper mongoDBHelper) { _mongoDBHelper = mongoDBHelper; } public void Add<T>() { _mongoDBHelper.AddMongoDB<T>(); } public void Delete<T>() { _mongoDBHelper.DeleteMongoDB<T>(); } public void Update<T>() { _mongoDBHelper.UpdateMongoDB<T>(); } public void Query<T>() { _mongoDBHelper.QueryMongoDB<T>(); } } }
那為什麼說組合優於繼承呢?
二者都會先構造一個MongoDBHelper,但是繼承是強侵入,父類的東西子類必須有
靈活性,繼承只為一個類服務;組合可以面向抽象為多個類型服務
2、代理模式(Proxy Pattern)
代理模式:
包一層:沒有什麼技術問題是包一層不能解決的,如果有,就再包一層。
vpn代理 翻牆代理 火車票代理。。。
通過代理業務類去完成對真實業務類的調用,代理類不能增加業務功能。
通過代理,能夠為對象擴展功能(不是增加業務)而不去修改原始業務類,也就是包了一層,我的地盤聽我的。比如來個日誌記錄,異常處理,可以避免修改業務類,只需要修改代理類。
場景:我們都知道如果要真實的去火車站買票則去火車站的路上需要發費一段時間,到了火車站可能查詢火車票也要發費一點時間,查詢到有票了這時候又需要排隊買票,又要發掉一段時間。為瞭解決這個問題,這個時候就有可能出現很多火車票代理點,這些代理點就可以代理查詢火車票和售票業務。代理點只負責代理相關業務,不進行增加業務,不會出現比如說火車站沒有提供選座業務,但是代理點卻增加了這個業務。下麵我們通過代碼來看下。
真實業務:
using System; namespace ProxyPattern { /// <summary> /// 售票業務介面 /// </summary> public interface ISellTickets { bool GetSomething(); void DoSomething(); } }
using System; using System.Threading; namespace ProxyPattern { /// <summary> /// 真實售票業務類 /// 一個耗時耗資源的對象方法 /// 一個第三方封裝的類和方法 /// </summary> public class RealSellTickets : ISellTickets { public RealSellTickets() { Thread.Sleep(2000); long lResult = 0; for (int i = 0; i < 100000000; i++) { lResult += i; } Console.WriteLine("RealSellTickets被構造。。。"); } /// <summary> /// 火車站查詢火車票 /// </summary> public bool GetSomething() { Console.WriteLine("坐車去火車站看看餘票信息。。。"); Thread.Sleep(3000); Console.WriteLine("到火車站,看到是有票的"); return true; } /// <summary> /// 火車站買票 /// </summary> public void DoSomething() { Console.WriteLine("開始排隊。。。"); Thread.Sleep(2000); Console.WriteLine("終於買到票了。。。"); } } }
代理類:
using System; using System.Collections.Generic; namespace ProxyPattern { /// <summary> /// 售票業務代理類 /// </summary> public class ProxySellTickets : ISellTickets { private readonly ISellTickets _sellTickets = new RealSellTickets(); //private static ISellTickets _sellTickets = new RealSellTickets(); //構造函數耗時耗資源,可改成單例 public void DoSomething() { try { Console.WriteLine("prepare DoSomething..."); //此處可進行擴展功能,比如來個日誌記錄,可以避免修改業務類,只需要修改代理類。 _sellTickets.DoSomething(); //代理業務 } catch (Exception ex) { Console.WriteLine(ex.Message); //此處可進行異常處理的擴展 throw ex; } } private static Dictionary<string, bool> _proxyDictionary = new Dictionary<string, bool>(); //緩存處理 public bool GetSomething() { try { Console.WriteLine("prepare GetSomething..."); string key = "Proxy_GetSomething"; bool bResult = false; if (!_proxyDictionary.ContainsKey(key)) { bResult = _sellTickets.GetSomething(); //代理業務 _proxyDictionary.Add(key, bResult); } else { bResult = _proxyDictionary[key]; } return bResult; } catch (Exception ex) { Console.WriteLine(ex.Message); throw ex; } } } }
可以看出ProxySellTickets完成了對RealSellTickets的代理,其實就是包了一層。通過代理業務類去完成對真實業務類的調用,代理類不能增加業務功能,但是可以擴展一些功能,比如日誌記錄、緩存處理、異常處理等。
3、裝飾器模式(Decorator Pattern)
裝飾器模式:
結構型設計模式巔峰之作,組合+繼承。
通過組合+繼承,完成對象功能動態擴展。
場景:很多人應該都上過網課,知道不同的VIP學員可能有不同的學習許可權,比如:具有觀看視頻直播許可權的普通VIP學員、具有視頻回看許可權的VIP學員、具有課程免費升級的VIP學員,另外可能還有點評、鞏固練習等許可權。現在我們希望為不同的學員去定製不同的許可權,這些許可權的個數能隨意定製,順序也能隨意定製,並且隨著業務的變化許可權類型可能還在不斷的增加,這時候我們又希望在不改變原有代碼的情況下還能去動態的擴展新的功能。這種場景就可以考慮使用裝飾器模式了。
下麵我們來重點看下代碼:
using System; namespace DecoratorPattern { /// <summary> /// 抽象學員 /// </summary> public abstract class AbstractStudent { public int Id { get; set; } public string Name { get; set; } public abstract void Study(); } }
using System; namespace DecoratorPattern { /// <summary> /// 一個普通的vip學員,學習vip課程 /// </summary> public class StudentVip : AbstractStudent { public override void Study() { Console.WriteLine("{0} is a vip student studying .net Vip", base.Name); } } }
using System; namespace DecoratorPattern.Decorator { /// <summary> /// 裝飾器的基類 /// 繼承+組合 /// 也是一個學員,繼承了抽象類 /// </summary> public class BaseStudentDecorator : AbstractStudent { private readonly AbstractStudent _student; //用了組合加override public BaseStudentDecorator(AbstractStudent student) { this._student = student; } public override void Study() { this._student.Study(); //Console.WriteLine("******************************"); //基類裝飾器必須是個空的行為 會重覆 } } }
using System; namespace DecoratorPattern.Decorator { /// <summary> /// 鞏固練習 /// </summary> public class StudentHomeworkDecorator : BaseStudentDecorator { public StudentHomeworkDecorator(AbstractStudent student) : base(student) //調用父類的構造函數 { } public override void Study() { base.Study(); Console.WriteLine("鞏固練習"); } } }
using System; namespace DecoratorPattern.Decorator { /// <summary> /// 點評 /// 父類是BaseStudentDecorator,爺爺類是AbstractStudent /// </summary> public class StudentCommentDecorator : BaseStudentDecorator { public StudentCommentDecorator(AbstractStudent student) : base(student) //調用父類的構造函數 { } public override void Study() { base.Study(); Console.WriteLine("點評"); } } }
using System; namespace DecoratorPattern.Decorator { /// <summary> /// 視頻代碼回看 /// </summary> public class StudentVideoDecorator : BaseStudentDecorator { public StudentVideoDecorator(AbstractStudent student) : base(student) //調用父類的構造函數 { } public override void Study() { base.Study(); Console.WriteLine("視頻代碼回看"); } } }
using System; namespace DecoratorPattern.Decorator { /// <summary> /// 課程免費升級 /// </summary> public class StudentUpdateDecorator : BaseStudentDecorator { public StudentUpdateDecorator(AbstractStudent student) : base(student) //調用父類的構造函數 { } public override void Study() { base.Study(); Console.WriteLine("課程免費升級"); } } }
使用(核心):
using System; using DecoratorPattern.Decorator; namespace DecoratorPattern { /// <summary> /// 裝飾器模式:結構型設計模式巔峰之作,組合+繼承。 /// 通過組合+繼承,完成對象功能動態擴展。 /// </summary> class Program { static void Main(string[] args) { { AbstractStudent student = new StudentVip() { Id = 381, Name = "隔壁老王" }; student = new StudentHomeworkDecorator(student); //鞏固練習 student = new StudentCommentDecorator(student); //點評 student = new StudentVideoDecorator(student); //視頻代碼回看 student = new StudentUpdateDecorator(student); //課程免費升級 student.Study(); //先調用基類Study方法,再處理自己的邏輯 } Console.ReadKey(); } } }
運行結果:
PS:裝飾器基類的Study方法中只能有代理業務,不能有別的,不然會重覆。
Demo源碼:
鏈接:https://pan.baidu.com/s/1EACr1x3VkiNL-tMaqknDnw 提取碼:ud7f
此文由博主精心撰寫轉載請保留此原文鏈接:https://www.cnblogs.com/xyh9039/p/13301890.html
版權聲明:如有雷同純屬巧合,如有侵權請及時聯繫本人修改,謝謝!!!