代理模式(學習筆記)
1. 意圖
提供物件的替代品或其佔位符。代理控制著對於原物件的訪問,允許在將請求提交給物件前後進行一些處理
2. 動機
在面向物件的系統中,有些物件由於某種原因(比如物件建立的開銷很大,或者某些操作需要安全控制,或者需要程序外的訪問等),直接訪問物件會給使用者或者系統結構帶來很多麻煩
3. 適用性
- 延遲初始化(虛擬代理)。如果你有一個偶爾使用的重量級服務物件,一直保持該物件執行會消耗系統資源時,可使用代理模式
- 訪問控制(保護代理)。如果只希望特定客戶端使用服務物件,這裡的物件可以是作業系統中非常重要的部分,而客戶端則是各種已啟動的程式(包括惡意程式),此時可使用代理模式
- 本地執行遠端服務 (遠端代理)。適用於服務物件位於遠端伺服器上的情形。代理通過網路傳遞客戶端請求,負責處理所有與網路相關的複雜細節
- 記錄日誌請求(日誌記錄代理)。適用於當你需要儲存對於服務物件的請求歷史記錄時。代理可以在向服務傳遞請求前進行記錄
- 快取請求結果 (快取代理)。適用於需要快取客戶請求結果並對快取生命週期進行管理時,特別是當返回結果的體積非常大時。代理可對重複請求所需的相同結果進行快取,還可使用請求引數作為索引快取的鍵值
- 智慧引用。可在沒有客戶端使用某個重量級物件時立即銷燬該物件。
代理會將所有獲取了指向服務物件或其結果的客戶端記錄在案。代理會時不時地遍歷各個客戶端,檢查它們是否仍在執行。如果相應的客戶端列表為空,代理就會銷燬該服務物件,釋放底層系統資源。代理還可以記錄客戶端是否修改了服務物件。其他客戶端還可以複用未修改的物件
4. 結構
5. 效果
1)可以在客戶端毫無察覺的情況下控制服務物件
2)客戶端對服務物件的生命週期沒有特殊要求,你可以對生命週期進行管理
3)開閉原則。你可以在不對服務或客戶端做出修改的情況下建立新代理
4)Proxy模式可以對使用者隱藏另一種稱為copy-on-write的優化方式,該優化與根據需要建立物件有關。拷貝一個龐大而複雜的物件是一種開銷很大的操作,如果這個拷貝並沒有被修改,那麼開銷就沒有必要。可以用代理延遲這一拷貝過程。在實現copy-on-write時必須對實體進行引用計數。只有當用戶請求一個修改該實體的操作時,代理才會真正的拷貝它。此時,代理需要減少實體引用計數,當引用數目為零時,這個實體將被刪除
5)服務響應可能會延遲
6. 程式碼實現
some_cool_media_library/ThirdPartyYouTubeLib.java: 遠端服務介面
package proxy.some_cool_media_library; import java.util.HashMap; /** * @author GaoMing * @date 2021/7/20 - 19:25 */ public interface ThirdPartyYouTubeLib { HashMap<String, Video> popularVideos(); Video getVideo(String videoId); }
some_cool_media_library/ThirdPartyYouTubeClass.java: 遠端服務實現
package proxy.some_cool_media_library; import java.util.HashMap; /** * @author GaoMing * @date 2021/7/20 - 19:22 */ public class ThirdPartyYouTubeClass implements ThirdPartyYouTubeLib{ @Override public HashMap<String, Video> popularVideos() { connectToServer("http://www.youtube.com"); return getRandomVideos(); } @Override public Video getVideo(String videoId) { connectToServer("http://www.youtube.com/" + videoId); return getSomeVideo(videoId); } // ----------------------------------------------------------------------- // Fake methods to simulate network activity. They as slow as a real life. private int random(int min, int max) { return min + (int) (Math.random() * ((max - min) + 1)); } private void experienceNetworkLatency() { int randomLatency = random(5, 10); for (int i = 0; i < randomLatency; i++) { try { Thread.sleep(100); } catch (InterruptedException ex) { ex.printStackTrace(); } } } private void connectToServer(String server) { System.out.print("Connecting to " + server + "... "); experienceNetworkLatency(); System.out.print("Connected!" + "\n"); } private HashMap<String, Video> getRandomVideos() { System.out.print("Downloading populars... "); experienceNetworkLatency(); HashMap<String, Video> hmap = new HashMap<String, Video>(); hmap.put("catzzzzzzzzz", new Video("sadgahasgdas", "Catzzzz.avi")); hmap.put("mkafksangasj", new Video("mkafksangasj", "Dog play with ball.mp4")); hmap.put("dancesvideoo", new Video("asdfas3ffasd", "Dancing video.mpq")); hmap.put("dlsdk5jfslaf", new Video("dlsdk5jfslaf", "Barcelona vs RealM.mov")); hmap.put("3sdfgsd1j333", new Video("3sdfgsd1j333", "Programing lesson#1.avi")); System.out.print("Done!" + "\n"); return hmap; } private Video getSomeVideo(String videoId) { System.out.print("Downloading video... "); experienceNetworkLatency(); Video video = new Video(videoId, "Some video title"); System.out.print("Done!" + "\n"); return video; } }
some_cool_media_library/Video.java: 視訊檔案
package proxy.some_cool_media_library; /** * @author GaoMing * @date 2021/7/20 - 19:22 */ public class Video { public String id; public String title; public String data; Video(String id, String title) { this.id = id; this.title = title; this.data = "Random video."; } }
proxy/YouTubeCacheProxy.java: 快取代理
package proxy.proxy; import proxy.some_cool_media_library.ThirdPartyYouTubeClass; import proxy.some_cool_media_library.ThirdPartyYouTubeLib; import proxy.some_cool_media_library.Video; import java.util.HashMap; /** * @author GaoMing * @date 2021/7/20 - 19:23 */ public class YouTubeCacheProxy implements ThirdPartyYouTubeLib{ private ThirdPartyYouTubeLib youtubeService; private HashMap<String, Video> cachePopular = new HashMap<String, Video>(); private HashMap<String, Video> cacheAll = new HashMap<String, Video>(); public YouTubeCacheProxy() { this.youtubeService = new ThirdPartyYouTubeClass(); } @Override public HashMap<String, Video> popularVideos() { if (cachePopular.isEmpty()) { cachePopular = youtubeService.popularVideos(); } else { System.out.println("Retrieved list from cache."); } return cachePopular; } @Override public Video getVideo(String videoId) { Video video = cacheAll.get(videoId); if (video == null) { video = youtubeService.getVideo(videoId); cacheAll.put(videoId, video); } else { System.out.println("Retrieved video '" + videoId + "' from cache."); } return video; } public void reset() { cachePopular.clear(); cacheAll.clear(); } }
downloader/YouTubeDownloader.java: 媒體下載應用
package proxy.downloader; import proxy.some_cool_media_library.ThirdPartyYouTubeLib; import proxy.some_cool_media_library.Video; import java.util.HashMap; /** * @author GaoMing * @date 2021/7/20 - 19:23 */ public class YouTubeDownloader { private ThirdPartyYouTubeLib api; public YouTubeDownloader(ThirdPartyYouTubeLib api) { this.api = api; } public void renderVideoPage(String videoId) { Video video = api.getVideo(videoId); System.out.println("\n-------------------------------"); System.out.println("Video page (imagine fancy HTML)"); System.out.println("ID: " + video.id); System.out.println("Title: " + video.title); System.out.println("Video: " + video.data); System.out.println("-------------------------------\n"); } public void renderPopularVideos() { HashMap<String, Video> list = api.popularVideos(); System.out.println("\n-------------------------------"); System.out.println("Most popular videos on YouTube (imagine fancy HTML)"); for (Video video : list.values()) { System.out.println("ID: " + video.id + " / Title: " + video.title); } System.out.println("-------------------------------\n"); } }
Demo.java: 初始化程式碼
package proxy; import proxy.downloader.YouTubeDownloader; import proxy.proxy.YouTubeCacheProxy; import proxy.some_cool_media_library.ThirdPartyYouTubeClass; /** * @author GaoMing * @date 2021/7/20 - 19:21 */ public class Demo { public static void main(String[] args) { YouTubeDownloader naiveDownloader = new YouTubeDownloader(new ThirdPartyYouTubeClass()); YouTubeDownloader smartDownloader = new YouTubeDownloader(new YouTubeCacheProxy()); long naive = test(naiveDownloader); long smart = test(smartDownloader); System.out.print("Time saved by caching proxy: " + (naive - smart) + "ms"); } private static long test(YouTubeDownloader downloader) { long startTime = System.currentTimeMillis(); // User behavior in our app: downloader.renderPopularVideos(); downloader.renderVideoPage("catzzzzzzzzz"); downloader.renderPopularVideos(); downloader.renderVideoPage("dancesvideoo"); // Users might visit the same page quite often. downloader.renderVideoPage("catzzzzzzzzz"); downloader.renderVideoPage("someothervid"); long estimatedTime = System.currentTimeMillis() - startTime; System.out.print("Time elapsed: " + estimatedTime + "ms\n"); return estimatedTime; } }
執行結果
Connecting to http://www.youtube.com... Connected! Downloading populars... Done! ------------------------------- Most popular videos on YouTube (imagine fancy HTML) ID: sadgahasgdas / Title: Catzzzz.avi ID: asdfas3ffasd / Title: Dancing video.mpq ID: 3sdfgsd1j333 / Title: Programing lesson#1.avi ID: mkafksangasj / Title: Dog play with ball.mp4 ID: dlsdk5jfslaf / Title: Barcelona vs RealM.mov ------------------------------- Connecting to http://www.youtube.com/catzzzzzzzzz... Connected! Downloading video... Done! ------------------------------- Video page (imagine fancy HTML) ID: catzzzzzzzzz Title: Some video title Video: Random video. ------------------------------- Connecting to http://www.youtube.com... Connected! Downloading populars... Done! ------------------------------- Most popular videos on YouTube (imagine fancy HTML) ID: sadgahasgdas / Title: Catzzzz.avi ID: asdfas3ffasd / Title: Dancing video.mpq ID: 3sdfgsd1j333 / Title: Programing lesson#1.avi ID: mkafksangasj / Title: Dog play with ball.mp4 ID: dlsdk5jfslaf / Title: Barcelona vs RealM.mov ------------------------------- Connecting to http://www.youtube.com/dancesvideoo... Connected! Downloading video... Done! ------------------------------- Video page (imagine fancy HTML) ID: dancesvideoo Title: Some video title Video: Random video. ------------------------------- Connecting to http://www.youtube.com/catzzzzzzzzz... Connected! Downloading video... Done! ------------------------------- Video page (imagine fancy HTML) ID: catzzzzzzzzz Title: Some video title Video: Random video. ------------------------------- Connecting to http://www.youtube.com/someothervid... Connected! Downloading video... Done! ------------------------------- Video page (imagine fancy HTML) ID: someothervid Title: Some video title Video: Random video. ------------------------------- Time elapsed: 9354ms Connecting to http://www.youtube.com... Connected! Downloading populars... Done! ------------------------------- Most popular videos on YouTube (imagine fancy HTML) ID: sadgahasgdas / Title: Catzzzz.avi ID: asdfas3ffasd / Title: Dancing video.mpq ID: 3sdfgsd1j333 / Title: Programing lesson#1.avi ID: mkafksangasj / Title: Dog play with ball.mp4 ID: dlsdk5jfslaf / Title: Barcelona vs RealM.mov ------------------------------- Connecting to http://www.youtube.com/catzzzzzzzzz... Connected! Downloading video... Done! ------------------------------- Video page (imagine fancy HTML) ID: catzzzzzzzzz Title: Some video title Video: Random video. ------------------------------- Retrieved list from cache. ------------------------------- Most popular videos on YouTube (imagine fancy HTML) ID: sadgahasgdas / Title: Catzzzz.avi ID: asdfas3ffasd / Title: Dancing video.mpq ID: 3sdfgsd1j333 / Title: Programing lesson#1.avi ID: mkafksangasj / Title: Dog play with ball.mp4 ID: dlsdk5jfslaf / Title: Barcelona vs RealM.mov ------------------------------- Connecting to http://www.youtube.com/dancesvideoo... Connected! Downloading video... Done! ------------------------------- Video page (imagine fancy HTML) ID: dancesvideoo Title: Some video title Video: Random video. ------------------------------- Retrieved video 'catzzzzzzzzz' from cache. ------------------------------- Video page (imagine fancy HTML) ID: catzzzzzzzzz Title: Some video title Video: Random video. ------------------------------- Connecting to http://www.youtube.com/someothervid... Connected! Downloading video... Done! ------------------------------- Video page (imagine fancy HTML) ID: someothervid Title: Some video title Video: Random video. ------------------------------- Time elapsed: 5875ms Time saved by caching proxy: 3479msView Code
7. 與其他模式的關係
- 介面卡模式能為被封裝物件提供不同的介面,代理模式能為物件提供相同的介面,裝飾模式則能為物件提供加強的介面
- 外觀模式與代理的相似之處在於它們都快取了一個複雜實體並自行對其進行初始化。但是,代理與其服務物件遵循同一介面,其與服務物件可以互換
- 裝飾和代理有著相似的結構,但是其意圖卻非常不同。這兩個模式的構建都基於組合原則,也就是說一個物件應該將部分工作委派給另一個物件。兩者之間的不同之處在於代理通常自行管理其服務物件的生命週期,而裝飾的生成則總是由客戶端進行控制
8. 已知應用
java.lang.reflect.Proxy
識別方法:代理模式會將所有實際
工作委派給一些其他物件。除非代理是某個服務的子類,否則每個代理方法最後都應該引用一個服務物件