掌握C# 中的代理設(shè)計(jì)模式(Proxy Design Pattern)
什么是代理設(shè)計(jì)模式?
根據(jù) Gang of four 的定義,代理設(shè)計(jì)模式為另一個(gè)對象提供了一個(gè)代理(代表其他行動(dòng)人)或占位符來控制對它的訪問。 代理是指“代替”或“代表”。
在最簡單的形式中,我們可以將代理定義為一個(gè)類,作為其他東西的接口。 代理可以連接到任何東西,例如網(wǎng)絡(luò)連接、內(nèi)存中的大型對象、文件或其他一些昂貴或無法復(fù)制的資源。
我們也可以說代理(Proxy)是客戶端(Client)調(diào)用的對象,用于訪問幕后的真實(shí)對象。 這意味著,在代理設(shè)計(jì)模式中,一個(gè)類代表另一個(gè)類的功能。
通過示例了解 C# 中的代理設(shè)計(jì)模式:
請查看下圖以更好地理解 C# 中的代理設(shè)計(jì)模式。 正如您在下圖中看到的,當(dāng)客戶端想要使用真實(shí)對象的某些方法時(shí),他/她需要通過代理對象。 這意味著客戶端將調(diào)用代理對象的方法,而代理將負(fù)責(zé)調(diào)用真實(shí)對象的方法。
代理的類型:
有三種類型的代理。 它們?nèi)缦隆?/span>
- 虛擬代理:虛擬代理是“昂貴創(chuàng)建”對象的占位符。 真正的對象僅在客戶端首次請求或訪問該對象時(shí)創(chuàng)建。
- 遠(yuǎn)程代理:遠(yuǎn)程代理為駐留在不同地址空間中的對象提供本地表示。
- 保護(hù)代理:保護(hù)代理控制對敏感對象的訪問。 代理對象在轉(zhuǎn)發(fā)請求之前檢查調(diào)用者是否具有所需的訪問權(quán)限。
代理設(shè)計(jì)模式真實(shí)示例:
請看下圖。 在右側(cè),您可以看到銀行,在左側(cè)可以看到一個(gè)叫 Anurag 的人。 Anurag 在銀行有一個(gè)賬戶。 在早些時(shí)候,比如說 1960 年,Anurag 想從他的賬戶中取款。 那么他要做的就是,他必須帶著他的存折去銀行。 然后他必須填寫表格并需要排隊(duì)。 輪到他時(shí),他必須將表格和銀行存折交給銀行員工,然后銀行員工驗(yàn)證表格和他的存折,如果一切正常,則銀行員工將所需的錢交給 Anurag。
假設(shè) Anurag 現(xiàn)在想取錢。 因此,他現(xiàn)在能做的不是去銀行,而是拿著銀行卡到最近的 ATM。 然后他插入他的銀行卡并輸入密碼和提款金額。 然后 ATM 將與銀行通信并驗(yàn)證密碼和金額,如果一切正常,ATM 將把錢交給 Anurag。 Anurag 無需去銀行,直接從 ATM 取款。 因此,這里的銀行是真實(shí)對象,ATM 是代理。 我認(rèn)為這是代理設(shè)計(jì)模式的最佳真實(shí)示例。
為什么我們需要 C# 中的代理設(shè)計(jì)模式?
讓我們以代理服務(wù)器為例來了解代理設(shè)計(jì)模式的必要性。
位于客戶端應(yīng)用程序(例如 Web 瀏覽器)和真實(shí)服務(wù)器之間的服務(wù)器稱為代理服務(wù)器。 該代理服務(wù)器攔截所有傳入的真實(shí)服務(wù)器請求,以查看它是否可以自行完成請求。 如果不是,那么它會(huì)將請求轉(zhuǎn)發(fā)到真實(shí)服務(wù)器。
代理服務(wù)器有兩個(gè)主要目標(biāo)。 它們?nèi)缦拢?/span>
提高性能:
代理服務(wù)器可以極大地提高應(yīng)用程序的性能。 這是因?yàn)樗鼘⒄埱蟮慕Y(jié)果保存了一段時(shí)間。 例如,假設(shè)我們有兩個(gè)用戶 X 和 Y,他們想通過代理服務(wù)器訪問特定資源。 首先,用戶 X 請求一個(gè)特定的資源(比方說一個(gè)員工列表)并將該資源緩存一段時(shí)間。 稍后,用戶 Y 也請求相同的資源,代理服務(wù)器不再將該請求轉(zhuǎn)發(fā)給實(shí)際服務(wù)器(這是一項(xiàng)耗時(shí)操作),只需從緩存中返回?cái)?shù)據(jù)即可。 由于客戶端和代理服務(wù)器在同一個(gè)網(wǎng)絡(luò)中,因此操作速度會(huì)快得多。
過濾請求:
代理服務(wù)器也可用于過濾傳入的請求。 例如,一家公司可能會(huì)使用代理服務(wù)器來阻止其員工訪問一組特定的網(wǎng)站,如某寶、拼某多等。
C#中代理設(shè)計(jì)模式的實(shí)現(xiàn)(保護(hù)代理):
業(yè)務(wù)要求:
請看下圖。 正如您在下圖中看到的,在右側(cè)我們有一臺(tái)共享文件夾的共享計(jì)算機(jī)。 在左側(cè),我們有在軟件農(nóng)場工作的員工。 共享計(jì)算機(jī)包含一個(gè)包含機(jī)密信息的共享文件夾,只有具有經(jīng)理和首席執(zhí)行官角色的員工才能訪問此共享文件夾并執(zhí)行讀寫操作。 另一方面,如果員工是開發(fā)人員,則不應(yīng)允許訪問共享文件夾。 那就是我們需要做某種保護(hù)。 在這種情況下,保護(hù)代理可以派上用場。
我們在這里可以做的是,在員工和共享計(jì)算機(jī)之間,我們需要引入文件夾代理。 這個(gè)文件夾代理可以做的是,它會(huì)檢查員工的角色是經(jīng)理還是首席執(zhí)行官,然后允許員工訪問共享文件夾并執(zhí)行讀寫操作。 另一方面,如果員工角色是 Developer 那么它會(huì)說你沒有權(quán)限訪問這個(gè)文件夾。 保護(hù)邏輯我們可以寫在文件夾代理中。
現(xiàn)在,我希望您了解代理設(shè)計(jì)模式。 那么,讓我們一步步在C#中實(shí)現(xiàn)代理設(shè)計(jì)模式吧。
第一步:創(chuàng)建員工類
創(chuàng)建一個(gè)名為 Employee.cs 的類文件,然后將以下代碼復(fù)制并粘貼到其中。
Step2:創(chuàng)建主體
創(chuàng)建一個(gè)名為 ISharedFolder 的接口,然后將以下代碼復(fù)制并粘貼到其中。 該接口定義了將由 RealSubject 和 Proxy 類實(shí)現(xiàn)的常用方法。
第三步:創(chuàng)建真實(shí)對象
創(chuàng)建一個(gè)名為 SharedFolder.cs 的類文件,然后將以下代碼復(fù)制并粘貼到其中。 此類實(shí)現(xiàn)主體 (ISharedFolder) 接口。
第四步:創(chuàng)建代理對象
創(chuàng)建一個(gè)名為 SharedFolderProxy.cs 的類文件,然后將以下代碼復(fù)制并粘貼到其中。 此類還實(shí)現(xiàn)了 Subject (ISharedFolder) 接口,并且它還持有對真實(shí)對象的引用。
Step5:客戶端代碼
請修改 Main 方法,如下所示。
了解代理設(shè)計(jì)模式類圖:
為了理解 C# 中代理設(shè)計(jì)模式的類圖,請看下圖。
如上圖所示,代理設(shè)計(jì)模式涉及三個(gè)參與者。 它們?nèi)缦拢?/span>
- 主體(ISharedFolder): 這是一個(gè)定義將由 RealSubject 和 Proxy 類實(shí)現(xiàn)的成員的接口,以便可以在任何需要 RealSubject 的地方使用 Proxy。 在我們的示例中,它是 ISharedFolder 接口。
- 真實(shí)對象(SharedFolder): 這是一個(gè)我們希望通過使用代理類來更高效地使用的類。 在我們的示例中,它是 SharedFolder 類。
- 代理(SharedFolderProxy): 這是一個(gè)持有對 RealSubject 類的引用的類,可以根據(jù)需要訪問 RealSubjectr 類成員。 它必須實(shí)現(xiàn)與 RealSubject 相同的接口,以便兩者可以互換使用。 在我們的示例中,它是 SharedFolderProxy 類。
何時(shí)在 C# 實(shí)時(shí)應(yīng)用程序中使用代理設(shè)計(jì)模式?
以下是您可以在 C# 實(shí)時(shí)應(yīng)用程序中使用代理設(shè)計(jì)模式的一些實(shí)時(shí)場景。
- 添加對現(xiàn)有對象的安全訪問。 代理將確定客戶端是否可以訪問感興趣的對象。
- 簡化復(fù)雜對象的 API。 代理可以提供一個(gè)簡單的 API,這樣客戶端代碼就不必處理感興趣對象的復(fù)雜性。
- 為 Web 服務(wù)或 REST 資源等遠(yuǎn)程資源提供接口。
- 通過要求遠(yuǎn)程資源在訪問資源之前盡快開始操作來協(xié)調(diào)對遠(yuǎn)程資源的昂貴操作。
- 在不更改現(xiàn)有類代碼的情況下向現(xiàn)有類添加線程安全功能。
C# 中的代理設(shè)計(jì)模式實(shí)時(shí)示例 – 虛擬代理
虛擬代理是創(chuàng)建成本高昂的對象的占位符。 真正的對象只有在客戶第一次請求或訪問一個(gè)對象時(shí)才被創(chuàng)建。 讓我們通過一個(gè)實(shí)時(shí)示例來理解這一點(diǎn)。 請看下面的圖片。 在右側(cè),您可以看到系統(tǒng)A,它有一個(gè) 200 MB 的圖像(老虎圖像)。 在左側(cè),您可以看到客戶端。 在客戶端和系統(tǒng)A 之間,有充當(dāng)虛擬代理的系統(tǒng)B。
比方說,客戶端第一次向系統(tǒng) B(虛擬代理)發(fā)送請求以顯示老虎圖像。 虛擬代理(即系統(tǒng) B)要做的是,首先它會(huì)檢查虛擬代理中是否存在真實(shí)圖像對象。 如果真實(shí)圖像對象不存在,那么在第 1 步中它將創(chuàng)建真實(shí)圖像對象并從磁盤加載圖像,在第 2 步中它將調(diào)用真實(shí)圖像對象上的顯示圖像方法。 虛擬代理還保存在步驟 1 中創(chuàng)建的真實(shí)圖像對象。 第 1 步,即創(chuàng)建真實(shí)圖像對象并從磁盤加載圖像是一項(xiàng)昂貴的操作。
假設(shè)客戶端第二次向虛擬代理發(fā)出相同的請求以顯示老虎圖像。 現(xiàn)在,虛擬代理要做的是,檢查實(shí)像對象是否存在,它發(fā)現(xiàn)虛擬代理中存在實(shí)像對象(這是因?yàn)樵诘谝粋€(gè)請求中,虛擬代理持有真實(shí)- 圖像對象)。 所以,虛擬代理要做的是,它不會(huì)執(zhí)行第 1 步,即它不會(huì)創(chuàng)建真實(shí)圖像對象并從磁盤加載圖像。 相反,它將使用現(xiàn)有的真實(shí)圖像對象并調(diào)用 Display Image 方法,即 step2。 所以,通過這種方式,使用代理設(shè)計(jì)模式,我們可以避免一次又一次地創(chuàng)建一個(gè)昂貴的對象。
類圖:
請看下圖:
C#中代理設(shè)計(jì)模式實(shí)時(shí)實(shí)例的實(shí)現(xiàn):
讓我們使用代理設(shè)計(jì)模式逐步實(shí)現(xiàn)上述實(shí)時(shí)示例。 眾所周知,代理設(shè)計(jì)模式涉及三個(gè)組件,例如主題、真實(shí)對象和代理對象。 讓我們一一實(shí)現(xiàn)上面的組件。
第一步:創(chuàng)建主體
這將是一個(gè)接口。 因此,創(chuàng)建一個(gè)名為 IImage 的接口,然后將以下代碼復(fù)制并粘貼到其中。 該接口提供將由真實(shí)對象和代理對象具體類實(shí)現(xiàn)的功能。 在我們的示例中,接口定義了一種方法,即 DisplayImage
步驟2:創(chuàng)建真實(shí)對象
這將是一個(gè)具體類,此類實(shí)現(xiàn) IImage 接口并提供 DisplayImage 方法的實(shí)現(xiàn)。 因此,創(chuàng)建一個(gè)名為 RealImage.cs 的類文件,然后將以下代碼復(fù)制并粘貼到其中。 RealImage 類的這個(gè)構(gòu)造函數(shù)將文件名作為參數(shù),然后從磁盤加載文件。
注意:這里的對象創(chuàng)建過程中有一個(gè)昂貴的操作。 這是因?yàn)樵趧?chuàng)建對象時(shí),它將從磁盤加載圖像。 LoadImageFromDisk 方法用于從磁盤加載圖像。 DisplayImage 方法只是用來顯示圖像。
第三步:創(chuàng)建代理
這將是一個(gè)具體類,它還實(shí)現(xiàn) IImage 接口并提供 DisplayImage 方法的實(shí)現(xiàn)。 因此,創(chuàng)建一個(gè)名為 ProxyObject.cs 的類文件,然后將以下代碼復(fù)制并粘貼到其中。 作為 DisplayImage 方法的一部分,首先,我們檢查 realImage 實(shí)例是否為 null。 如果為 null,則我們將創(chuàng)建實(shí)例,然后在 realImage 實(shí)例上調(diào)用 DisplayImage 方法。 另一方面,如果 realImage 實(shí)例不為 null,則它不會(huì)創(chuàng)建該實(shí)例,而是使用現(xiàn)有的 realImage 實(shí)例來調(diào)用 DisplayImage 方法。
第四步:客戶端
請修改 Main 方法,如下所示。 首先,我們創(chuàng)建ProxyImage對象來顯示老虎圖像,然后調(diào)用3次DisplayImage方法。 在這種情況下,第一次調(diào)用 DisplayImage 方法將創(chuàng)建 RealImage 實(shí)例,因此它將從磁盤加載圖像。 但是從第二次調(diào)用 DisplayImage 方法開始,它將使用現(xiàn)有的 RealImage 實(shí)例,因此不會(huì)從磁盤加載圖像。 這個(gè)過程對于第二次創(chuàng)建代理對象以顯示獅子圖像也是相同的。