代理模式(Proxy)
1.1.1 摘要
今天是父親節(jié),首先祝爸爸父親節(jié)快樂身體健康,隨著互聯(lián)網(wǎng)飛速的發(fā)展,現(xiàn)在許多軟件系統(tǒng)都提供跨網(wǎng)絡(luò)和系統(tǒng)的應(yīng)用,但在跨網(wǎng)絡(luò)和系統(tǒng)應(yīng)用時(shí),作為系統(tǒng)開發(fā)者并不希望客戶直接訪問系統(tǒng)中的對(duì)象。其中原因很多考慮到系統(tǒng)安全和性能因素,這時(shí)候聰明的開發(fā)者想到了在客戶端和系統(tǒng)端添加一層中間層----代理層,也是即將要介紹的代理模式。
- 定義
代理模式(Proxy)為另一個(gè)對(duì)象提供一個(gè)替身或占位符以控制對(duì)這個(gè)對(duì)象的訪問,簡(jiǎn)而言之就是用一個(gè)對(duì)象來代表另一個(gè)對(duì)象。
- 意圖
提供其他對(duì)象一個(gè)代理或占位符,來控制該對(duì)象的訪問權(quán)限。
- 動(dòng)機(jī)
為什么我們要控制對(duì)象的訪問權(quán)限呢?其中一個(gè)原因是通過控制來延遲對(duì)象的創(chuàng)建和實(shí)例化,直到真正需要使用該對(duì)象才進(jìn)行創(chuàng)建和實(shí)例化。由于一些對(duì)象創(chuàng)建和實(shí)例化需要占用大量系統(tǒng)資源,但我們并不能確定用戶一定會(huì)調(diào)用該對(duì)象,所以通過延遲對(duì)象實(shí)例化來減緩系統(tǒng)資源的消耗。例如文檔編輯器如word,我們可以在里面插入鏈接、圖片等,但是并不是我們每次打開word時(shí)都有創(chuàng)建和實(shí)例化這些對(duì)象,特別是實(shí)例化圖片對(duì)象很消耗資源,而且我們有必要實(shí)例化所有圖片嗎?當(dāng)我們?cè)诓榭磜ord時(shí),只是看到其中的一部分,所以沒有必要實(shí)例化所以資源,當(dāng)我們看下一頁時(shí)再實(shí)例化也不遲。
圖1代理模式結(jié)構(gòu)圖
1.1.2 正文
軟件系統(tǒng)設(shè)計(jì)可以提供本地或遠(yuǎn)程的方法,隨著互聯(lián)網(wǎng)的發(fā)展特別是WebService技術(shù)的提出,使得更多軟件系統(tǒng)都提供遠(yuǎn)程方法調(diào)用。當(dāng)我們?cè)L問網(wǎng)絡(luò)上一臺(tái)計(jì)算機(jī)的資源時(shí),我們正在跨越網(wǎng)絡(luò)障礙,跨越網(wǎng)絡(luò)障礙有時(shí)候是非常復(fù)雜,因?yàn)橐_保數(shù)據(jù)安全可靠地傳輸。如果真的要我們都去解決那些復(fù)雜網(wǎng)絡(luò)問題,那么我估計(jì)程序員們瘋了。還好代理模式(Proxy)幫我們解決了其中的一些問題----WebService技術(shù)。
現(xiàn)在讓我們通過一個(gè)簡(jiǎn)單的加減乘除程序?yàn)槔f明什么是代理模式(Proxy)和如何實(shí)現(xiàn)。
現(xiàn)在我們服務(wù)器端提供計(jì)算方法,分別定義計(jì)算類Math和代理類MathProxy。然后我們的客戶端通過調(diào)研MathProxy來間接用Math類的計(jì)算方法。
/// <summary> /// Define a interface, make proxy and subject /// have the same methods. /// </summary> public interface IMath { /// <summary> /// Calc methods. /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <returns></returns> double Add(double x, double y); double Sub(double x, double y); double Mul(double x, double y); double Div(double x, double y); } /// <summary> /// A proxy class. /// </summary> public class MathProxy : IMath { /// <summary> /// MathProxy has a Math's reference. /// </summary> private Math _math; public MathProxy() { AppDomain ad = AppDomain.CreateDomain("MathDomain", null, null); /// Create a Math object. ObjectHandle obj = ad.CreateInstance( "Gof.DesignPattern.Proxy.Math", "Gof.DesignPattern.Proxy.Math.Math"); this._math = obj.Unwrap() as Math; } #region IMath 成員 public double Add(double x, double y) { return _math.Add(x, y); } public double Sub(double x, double y) { return _math.Sub(x, y); } public double Mul(double x, double y) { return _math.Mul(x, y); } public double Div(double x, double y) { return _math.Div(x, y); } #endregion } /// <summary> /// Due to Math inherits MarshalByRefObject /// so the class supports remoting. /// </summary> public class Math : MarshalByRefObject, IMath { #region IMath 成員 public double Add(double x, double y) { return x + y; } public double Sub(double x, double y) { return x - y; } public double Mul(double x, double y) { return x * y; } public double Div(double x, double y) { if (y == 0) { return 0; } return x / y; } #endregion } public class Program { static void Main(string[] args) { // Create math proxy. MathProxy prox = new MathProxy(); // Console.WriteLine(prox.Add(2, 6)); Console.WriteLine(prox.Sub(88, 2)); Console.WriteLine(prox.Mul(22, 55)); Console.WriteLine(prox.Div(5, 0)); Console.ReadKey(); } }
圖2網(wǎng)絡(luò)中代理模式
現(xiàn)在我們實(shí)現(xiàn)了Math類的代理模式,使得客戶端通過MathProxy代理類來調(diào)用Math提供的Add()、Sub()、Mul() 和Div() 方法。
但細(xì)心的你肯定發(fā)現(xiàn)問題了,我的程序根本沒有提供夸網(wǎng)絡(luò)的實(shí)現(xiàn),最多就是夸應(yīng)用程序的調(diào)用,的確我并沒有實(shí)現(xiàn)跨網(wǎng)絡(luò)的調(diào)用,那我們?cè)撊绾螌?shí)現(xiàn)跨網(wǎng)絡(luò)呢?還記得我們前面介紹的WebService技術(shù)嗎?要實(shí)現(xiàn)跨網(wǎng)絡(luò)可以選擇使用WebService來公開我們Math類中的方法,讓客戶端添加Math類的Web引用,從而WebService充當(dāng)了代理角色。
我們前面提到的圖片延遲加載的例子,這其中也是體現(xiàn)了代理模式的思想。例如我們?cè)诓榭匆恍┚W(wǎng)頁之后,我們的瀏覽器會(huì)保留一些網(wǎng)頁的信息,從而加快下次網(wǎng)頁加載的速度。現(xiàn)在讓我們通過一個(gè)簡(jiǎn)單WinForm圖片加載器程序說明:
圖片加載器首先查看我們本地是否已經(jīng)存在要加載的圖片,如果存在就直接加載圖片,如果不存在它會(huì)到網(wǎng)絡(luò)中下載圖片然后進(jìn)行加載。
圖3圖片加載器界面
由于時(shí)間的關(guān)系我們已經(jīng)把界面設(shè)計(jì)好了,現(xiàn)在我們要完成的是點(diǎn)擊Test Image Proxy按鈕的邏輯,首先我們圖片加載器會(huì)查看本地是否已經(jīng)存在圖片,如果不存在就到網(wǎng)絡(luò)上加載我們需要的圖片就OK了。
/// <summary> /// The image proxy /// If we have dowload the picture in local, /// then the program would get it, otherwise /// the program would get the picture in internet. /// </summary> private class ImageProxy { private static Image _image = null; private int _width = 123; private int _height = 154; private bool _retrieving = false; public int Width { get { return _image == null ? _width : _image.Width; } } public int Height { get { return _image == null ? _height : _image.Height; } } /// <summary> /// Gets the image. /// </summary> public Image Image { get { if (_image != null) { return _image; } else { if (!_retrieving) { _retrieving = true; //// Initialize a thread. Thread retrieveThread = new Thread( new ThreadStart(RetrieveImage)); //// Start thread. retrieveThread.Start(); } } //// If the picture in local, we get it directly. return PlaceHolderImage(); } } /// <summary> /// Places the holder image. /// Get the picture in local. /// </summary> /// <returns></returns> public Image PlaceHolderImage() { return new Bitmap(System.Reflection.Assembly.GetExecutingAssembly(). GetManifestResourceStream(this.GetType().Namespace.ToString() + ".googlefathersday.jpg")); } /// <summary> /// Retrieves the image. /// Get the picture in internet. /// </summary> public void RetrieveImage() { string url = @"http://www.google.com.hk/logos/2011/fathersday11-hp.jpg"; HttpWebRequest request = HttpWebRequest.Create(url) as HttpWebRequest; HttpWebResponse response = request.GetResponse() as HttpWebResponse; _image = Image.FromStream(response.GetResponseStream()); } }
上面我們完成了點(diǎn)擊加載的邏輯,我們提供一個(gè)圖片加載代理ImageProxy類,然后分別提供本地加載和網(wǎng)絡(luò)加載功能PlaceHolderImage()方法和RetrieveImage()方法,PlaceHolderImage()方法把圖片嵌入到程序中,而RetrieveImage()到網(wǎng)絡(luò)中加載圖片。
圖4圖片加載效果
這里我們使用了灰色和彩色圖片分別表示本地和網(wǎng)絡(luò)加載效果,現(xiàn)在我們通過代理模式(Proxy)完成了圖片延遲加載的效果。
我覺得《HeadFirst設(shè)計(jì)模式》中的糖果機(jī)代理模式比較有趣,現(xiàn)在也讓我們通過C#來實(shí)現(xiàn)糖果機(jī)代理模式。
問題描述:現(xiàn)在有些分布在不同地方的糖果機(jī)(類似自動(dòng)售賣機(jī)),我們要通過網(wǎng)絡(luò)方式獲得糖果機(jī)中的糖果數(shù)量和糖果機(jī)的狀態(tài)。
現(xiàn)在我們要添加三個(gè)項(xiàng)目分別是:GumballState.Machine、Host和Client(其中Host是WebService),在GumballState.Machine里面我們定義糖果機(jī)售賣邏輯和狀態(tài),Host把我們定義售賣機(jī)方法通過網(wǎng)絡(luò)形式發(fā)布,Client通過添加Web引用間接調(diào)用GumballState.Machine中的方法。
圖5糖果機(jī)程序
首先我們GumballState.Machine類添加五個(gè)方法分別是:StartWithQuarters()、InsertQuarter()、TurnCrank()、EjectQuarter()和GetReport()。
- StartWithQuarters()方法初始化糖果數(shù)量和修改糖果機(jī)狀態(tài)。
/// <summary> /// Starts the with quarters. /// Calc the current qty and set machine state. /// </summary> /// <param name="cnt">The CNT.</param> public void StartWithQuarters(int cnt) { this._cnt = cnt; if (this._cnt > 0) { this._state = GumballMachineState.NoQuarter; } }
- InsertQuarter()方法判斷用戶付款操作和修改糖果機(jī)狀態(tài)。
/// <summary> /// Inserts the quarter. /// The user inserts money /// </summary> public void InsertQuarter() { switch (this._state) { case (GumballMachineState.HasQuarter): this._logMsg.Append("You can't insert another quarter\n"); return; case (GumballMachineState.NoQuarter): this._state = GumballMachineState.HasQuarter; this._logMsg.Append("You inserted a quarter\n"); return; case (GumballMachineState.SoldOut): this._logMsg.Append("You can't insert a quarter, the machine is sold out\n"); return; case (GumballMachineState.Sold): this._logMsg.Append("Please wait, we're already giving you a gumball\n"); return; } }
- TurnCrank()方法判斷用戶確定購買操作和修改糖果機(jī)狀態(tài)。
/// <summary> /// Turns the crank. /// The user presses "OK" button to get gumball. /// </summary> public void TurnCrank() { if (this._state == GumballMachineState.SoldOut) { this._logMsg.Append("You turned, but there are no gumballs\n"); } else if (this._state == GumballMachineState.HasQuarter) { this._logMsg.Append("You turned please wait...\n"); this._state = GumballMachineState.Sold; this.Dispense(); } else if (this._state == GumballMachineState.NoQuarter) { this._logMsg.Append("You turned but there's no quarter\n"); } else { this._logMsg.Append("Turning twice doesn't get you another gumball!\n"); } }
- EjectQuarter()方法判斷用戶退款操作和修改糖果機(jī)狀態(tài)。
/// <summary> /// Ejects the quarter. /// The user cancels order and get money back. /// </summary> public void EjectQuarter() { switch (this._state) { case (GumballMachineState.HasQuarter): this._state = GumballMachineState.NoQuarter; this._logMsg.Append("Quarter returned\n"); return; case (GumballMachineState.NoQuarter): this._state = GumballMachineState.HasQuarter; this._logMsg.Append("You haven't inserted a quarter\n"); return; case (GumballMachineState.SoldOut): this._logMsg.Append("Sorry, you already turned the crank\n"); return; case (GumballMachineState.Sold): this._logMsg.Append("You can't eject, you haven't inserted a quarter yet\n"); return; } }
- GetReport()方法返回糖果機(jī)銷售量和狀態(tài)給管理者。
/// <summary> /// Gets the report. /// </summary> /// <returns></returns> public string GetReport() { StringBuilder result = new StringBuilder(); result.Append("\nMighty Gumball, Inc."); result.Append("\n.NET3.5-enabled Standing Gumball Model #2104\n"); result.Append("Inventory: " + this._cnt + " gumball"); if (this._cnt != 1) result.Append("s"); result.Append("\nMachine is "); if (_state == GumballMachineState.SoldOut) { result.Append("sold out"); } else if (_state == GumballMachineState.NoQuarter) { result.Append("waiting for quarter"); } else if (_state == GumballMachineState.HasQuarter) { result.Append("waiting for turn of crank"); } else if (_state == GumballMachineState.Sold) { result.Append("delivering a gumball"); } result.Append("\n"); string ret = this._logMsg.ToString() + "\n" + result.ToString(); this._logMsg = new StringBuilder(); return ret.ToString(); }
接著我們通過WebService把以上方法通過網(wǎng)絡(luò)形式發(fā)布,然后我們?cè)诳蛻舳颂砑觲eb引用來間接調(diào)用GumballState.Machine類中方法。
圖6客戶端添加web引用
現(xiàn)在我們完成了服務(wù)器端的功能了,接下來我們通過控制臺(tái)應(yīng)用程序作為客戶端調(diào)用服務(wù)器端公開的方法。
class Program { static void Main() { // Create proxy object GumballMachineClient proxy = new GumballMachineClient(); proxy.StartWithQuarters(5); proxy.InsertQuarter(); proxy.TurnCrank(); Console.WriteLine(proxy.GetReport()); proxy.InsertQuarter(); proxy.EjectQuarter(); proxy.TurnCrank(); Console.WriteLine(proxy.GetReport()); proxy.InsertQuarter(); proxy.TurnCrank(); proxy.InsertQuarter(); proxy.TurnCrank(); proxy.EjectQuarter(); Console.WriteLine(proxy.GetReport()); proxy.InsertQuarter(); proxy.InsertQuarter(); proxy.TurnCrank(); proxy.InsertQuarter(); proxy.TurnCrank(); proxy.InsertQuarter(); proxy.TurnCrank(); Console.WriteLine(proxy.GetReport()); // Wait for user Console.ReadKey(); } }
圖7客戶端調(diào)用web方法
現(xiàn)在我們通過控制臺(tái)應(yīng)用程序添加web引用之后,來調(diào)用GumballState.Machine類中方法,我們終于正在實(shí)現(xiàn)了一個(gè)跨網(wǎng)絡(luò)的應(yīng)用程序調(diào)用,WebService充當(dāng)了proxy的角色,而且?guī)臀覀兘鉀Q了很多網(wǎng)絡(luò)的問題使得我們實(shí)現(xiàn)跨網(wǎng)絡(luò)程序十分方便。
1.1.3 總結(jié)
代理模式(Proxy)根據(jù)種類不同,效果也不盡相同:
- 遠(yuǎn)程(Remote)代理:為一個(gè)位于不同的地址空間的對(duì)象提供一個(gè)局域代表對(duì)象。這個(gè)不同的地址空間可以是在本機(jī)器中,也可是在另一臺(tái)機(jī)器中。遠(yuǎn)程代理又叫做大使(Ambassador)。好處是系統(tǒng)可以將網(wǎng)絡(luò)的細(xì)節(jié)隱藏起來,使得客戶端不必考慮網(wǎng)絡(luò)的存在。客戶完全可以認(rèn)為被代理的對(duì)象是局域的而不是遠(yuǎn)程的,而代理對(duì)象承擔(dān)了大部份的網(wǎng)絡(luò)通訊工作。由于客戶可能沒有意識(shí)到會(huì)啟動(dòng)一個(gè)耗費(fèi)時(shí)間的遠(yuǎn)程調(diào)用,因此客戶沒有必要的思想準(zhǔn)備。
- 虛擬(Virtual)代理(圖片延遲加載的例子):根據(jù)需要?jiǎng)?chuàng)建一個(gè)資源消耗較大的對(duì)象,使得此對(duì)象只在需要時(shí)才會(huì)被真正創(chuàng)建。使用虛擬代理模式的好處就是代理對(duì)象可以在必要的時(shí)候才將被代理的對(duì)象加載;代理可以對(duì)加載的過程加以必要的優(yōu)化。當(dāng)一個(gè)模塊的加載十分耗費(fèi)資源的情況下,虛擬代理的好處就非常明顯。
- Copy-on-Write代理:虛擬代理的一種。把復(fù)制(克隆)拖延到只有在客戶端需要時(shí),才真正采取行動(dòng)。
- 智能引用(Smart Reference)代理:當(dāng)一個(gè)對(duì)象被引用時(shí),提供一些額外的操作,比如將對(duì)此對(duì)象調(diào)用的次數(shù)記錄下來等。
代理模式(Proxy)VS 裝飾者(Decorator)
意圖:它們都提供間接訪問對(duì)象層,都保存被調(diào)用對(duì)象的引用。
代理模式(Proxy):為另一個(gè)對(duì)象提供一個(gè)替身或占位符以控制對(duì)這個(gè)對(duì)象的訪問,簡(jiǎn)而言之就是用一個(gè)對(duì)象來代表另一個(gè)對(duì)象。
裝飾者(Decorator):動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé),就增加功能來說,Decorator模式比生成子類更為靈活,它避免了類爆炸問題,像裝飾者(Decorator),代理模式(Proxy)組成一個(gè)對(duì)象并提供相同的接口,但代理模式并不關(guān)心對(duì)象動(dòng)態(tài)職能的增減。
在代理模式(Proxy)中Subject定義了主要的功能,而且Proxy根據(jù)Subject提供功能控制對(duì)象的訪問權(quán)限。在裝飾者(Decorator)中Component只是提供了其中的一些功能,需要通過裝飾鏈動(dòng)態(tài)給對(duì)象增加職能。
|
|
關(guān)于作者:[作者]:
JK_Rush從事.NET開發(fā)和熱衷于開源高性能系統(tǒng)設(shè)計(jì),通過博文交流和分享經(jīng)驗(yàn),歡迎轉(zhuǎn)載,請(qǐng)保留原文地址,謝謝。 |

1.1.1 摘要 今天是父親節(jié),首先祝爸爸父親節(jié)快樂身體健康,隨著互聯(lián)網(wǎng)飛速的發(fā)展,現(xiàn)在許多軟件系統(tǒng)都提供跨網(wǎng)絡(luò)和系統(tǒng)的應(yīng)用,但在跨網(wǎng)絡(luò)和系統(tǒng)應(yīng)用時(shí),作為系統(tǒng)開發(fā)者并不希望客戶直接訪問系統(tǒng)中的對(duì)象。...








浙公網(wǎng)安備 33010602011771號(hào)