多語言網(wǎng)站開發(fā)公司市場調(diào)查報告
詩有可解不可解,若鏡花水月勿泥其跡可也
—— 謝榛
文章目錄
- 定義
- 圖紙
- 一個例子:圖片搜索器
- 圖片加載
- 搜索器
- 直接在Image添加
- 組合他們
- 各種各樣的代理
- 遠程代理:鏡中月,水中花
- 保護代理:對象也該有隱私
- 引用代理:我什么時候可以動手?
- 虛擬代理:我們真的需要全部信息嗎?
定義
為其他對象提供一種代理以控制對這個對象的訪問
圖紙
一個例子:圖片搜索器
某天,你突發(fā)奇想,想做一個可以展示指定文件夾內(nèi)所有圖片的桌面應(yīng)用。這個應(yīng)用很簡單,遍歷文件夾,發(fā)現(xiàn)圖片文件,把圖片加載到GUI上的圖片列表里,顯示圖片名和圖片,就像這樣:
圖片加載
為了實現(xiàn)這樣的效果,你編寫了自己的 Image 類簇,并給予他一個通過 InputStream 來載入圖片并獲取圖片信息的方法,就像這樣:
我們可以通過 Image 中的 loadByStream 方法把參數(shù)輸入流內(nèi)的圖片文件加載到內(nèi)存中來,并把獲取到的信息寫到 messageMap 中,根據(jù)需要獲取里面的內(nèi)容反饋給 client
至此,不出意外的話我們根據(jù) Image對象 提供的信息繪制出GUI后就可以得到截圖類似的效果
搜索器
隨著文件夾里面的圖片越來越多,在里面找到你需要的變得越來越困難,于是新的想法出現(xiàn)了,你想要加一個搜索框,用于篩選讀取到的圖片
這個需求很合理,但是實現(xiàn)起來卻出現(xiàn)了問題
Image 是通過把圖片載入到內(nèi)存的方式來獲取圖片信息的,這就意味著我要獲取所有圖片的文件名用于搜索之前必須加載所有的圖片,這是無法接受的
經(jīng)過我們分析,只要不把文件加載到內(nèi)存里,只是遍歷文件夾獲取其中的所有文件的文件名是很快的,通過File
也就是說,對于一個文件來說,他在程序里會同時擁有對應(yīng)的 Image對象 和 File對象
我當(dāng)然希望這兩個對象可以綁定在一起,那該怎么做呢?
直接在Image添加
直接添加到Image中,就像這樣:
看起來很美好,也很必要
可是我要為將來考慮,這個程序里面的 Image 不一定都從硬盤上讀取文件,我允許他從任何輸入流中加載圖片出來
這種時候file對象的存在就顯得很尷尬,而且會導(dǎo)致getName方法的異常,我又得給他寫 if(file==null)……
這顯然是很糟糕的設(shè)計
組合他們
既然直接添加不可以,那很顯然就只能用另一個類來把他們組合起來
但是怎么組合,有講究
組合他們的這個類本質(zhì)上還是Image
,我并不是為了用這個類的對象來復(fù)制/刪除文件……他的任務(wù)是包含了 Image 的任務(wù)的
所以我們可以考慮這樣做:
我們創(chuàng)建了一個 Image類 的 代理類 ImageFileProxy,這個代理類本質(zhì)上還是 Image,當(dāng)你調(diào)用他的 loadByStream 或者 getWidth 之類的方法時,他直接會去調(diào)用 super 對象的對應(yīng)方法
而當(dāng)你訪問 getName 的時候,他里面藏著的 File 會處理對應(yīng)的業(yè)務(wù),他才不管你現(xiàn)在有沒有把圖片加載完。這樣一來就可以實現(xiàn)一邊加載一邊查詢了
不要小看這種寫法,將來如果你突發(fā)奇想想在加載圖片的時候增加一個加載進度條,那也只是新增Image的子類而已,對已有的代碼不會有什么影響
那你會說,這種實現(xiàn)跟代理的圖紙差別太大了,這不就是我平時用的繼承嗎?
你可以試著把Image的接口抽象出來,并在ImageFileProxy的構(gòu)造方法中要求傳入Image對象,就像這樣:
是不是發(fā)現(xiàn)他成了一個標(biāo)準(zhǔn)的代理實現(xiàn)?
各種各樣的代理
遠程代理:鏡中月,水中花
遠程代理是指在不同的地址空間里提供對相同內(nèi)容的局部代表
是不是覺得這個定義老復(fù)雜了,emmmm,舉個例子,比如說數(shù)組的復(fù)制,就像這樣:
數(shù)組A里存著 X/Y/Z 三個對象,接著我們復(fù)制數(shù)組A得到數(shù)組B。數(shù)組A和B的內(nèi)存地址當(dāng)然是不一樣的,但B里面存的還是 X/Y/Z。操作B,其實跟操作A沒什么區(qū)別,其實此時B就能算是A的一個遠程代理
真正讓遠程代理廣為人知的是網(wǎng)絡(luò)相關(guān)的開發(fā)
比如說現(xiàn)在我有Java寫成的 服務(wù)器和N個客戶機,我希望在服務(wù)器上有個按鈕,點擊后可以直接獲取客戶機上的硬件信息。
要做成這個效果,根據(jù)不同的連接方式,實現(xiàn)方法各不相同。其中一種是利用 RMI
技術(shù),讓服務(wù)器直接調(diào)用客戶機上運行的對象里的方法,并獲取結(jié)果,這時候其實就是在服務(wù)器上建立客戶機的 遠程代理
是的,你沒看錯,我說的就是在服務(wù)器JVM上調(diào)用運行在其他JVM上的對象。這不是魔法,是真實可行的技術(shù),同時他也是分布式的基礎(chǔ),也是遠程代理大放異彩的舞臺
保護代理:對象也該有隱私
當(dāng)你需要管理N個具有相同根類對象的時候,十有八九會用到 容器,List也好,Set也好,或者數(shù)組、Map 這不重要
重要的是這些容器對象擁有對自己所存儲的對象的完全掌控權(quán),我的意思是說,client 可以隨自己喜好對容器里面的內(nèi)容增刪改查,這完全不受控
不是所有的容器都允許隨意往里添加或刪除的內(nèi)容的,這時候你就需要隱藏容器的某些接口
我們會再建一個類把真正的容器類封裝起來,接著不提供被隱藏的接口的訪問方式(或者根據(jù)不同的權(quán)限提供不同的行為)。而 client 只能和外部的 代理類
交互,至此實現(xiàn)對容器的保護,這就是保護代理
引用代理:我什么時候可以動手?
如果說工廠方法之類的模式提供了對一個對象的 創(chuàng)建行為的包裝
的話,那么引用代理就是 對一個對象提供從創(chuàng)建到銷毀全方面的包裝
因為 client 只能通過代理類對象來訪問被代理的對象,那么所有對被代理的對象的訪問都是在代理類對象的監(jiān)控之下的。只要你想,你可以知道被代理對象現(xiàn)在被多少個地方引用,他有沒有進入某個有鎖的區(qū)域,也可以決定被代理對象什么時候被初始化……
知道這些信息是有用的,打個比方:
- 知道多少個引用:可以做引用計數(shù)、可以在丟失所有引用時釋放資源
- 了解狀態(tài)是否被鎖:可以控制別的對象不允許修改被代理對象的狀態(tài)
- 決定何時初始化:是第一次被訪問時初始化?還是跟代理類對象一起被初始化?
引用代理可以給你的程序提供很多很偏門的優(yōu)化手段噠
虛擬代理:我們真的需要全部信息嗎?
代理模式可以提供對對象的一種訪問控制
這種控制可以是限制對象公開的接口(保護代理);也可以用來管理被代理對象何時釋放(引用代理)
他甚至可以做到在某個內(nèi)容沒有被加載進來的情況下展示他的一部分,這就是虛擬代理
試想以下,當(dāng)我們需要獲取一個文件的名字和修改時間,有必要把整個文件都加載到內(nèi)存里過一遍嗎?
答案必然是否定的。要不然你打開【我的電腦】里面的目錄一定要很久(因為你打開的時候需要過一遍文件夾里所有的文件)
在Java程序里,我們用 File 來表示一個文件,通過 File 對象的方法我們可以獲取到和他所代表的文件有關(guān)的各種信息
那么請問,File 在獲取硬盤上的文件的信息的時候,真的每次都會把整個文件加載到程序中嗎?
肯定沒有啊。倒不如說在你用 IO流 讓這個文件流入程序之前,硬盤上的那個文件根本沒有被載入
也就是說 File對象 和硬盤上的那個文件之間,也存在一種代理關(guān)系
File 為我們提供了一系列操作文件和讀取文件信息的接口;但是換句話來說, File對象 也控制著我們訪問文件的路徑和方式,讓我們一定是按照 File類 的編寫者的想法去跟文件交互,就像 getter&setter 一樣。