代理模式(Proxy Pattern)
本文是翻譯《SoftwareArchitectureDesignPatternsinJava》一書,我將陸續為大家翻譯其他章節,希望大家支持指正!第23章:代理模式(ProxyPattern)
描述:
讓我們思考一下下面的代碼://Client
- classCustomer{
- publicvoidsomeMethod(){
- //CreatetheServiceProviderInstance
- FileUtilfutilObj=newFileUtil();
- //AccesstheService
- futilObj.writeToFile(「SomeData」);
- }
- }
作為它實現的一部分,Customer類創建了一個FileUtil類的一個實例並且直接訪問它的服務。換句話說,對於客戶對象,訪問FileUtil對象的方式是很直接的。它的實現可能是客戶對象訪問服務提供者對象最為普通的方式了。相比較,有些時候客戶對象可能不直接訪問服務提供者(也就是指目標對象),這種情況是由於下面的原因導致的:(1)目標對象的位置??目標對象可能存在於同一臺或者不同機器的不同地址空間。(2)目標對象的存在形式??目標對象可能直到他被請求服務的時候還不存在,或者對象被壓縮。(3)特殊的行為??目標對象可以根據客戶對象的訪問許可權接受或拒絕服務。在多線程環境,一些服務提供者對象需要特殊的考慮。在這些情況下,代理模式(ProxyPattern)建議不要使有特殊需求的客戶對象直接訪問目標對象,而是使用一個單獨的(分離的)對象(也就是指代理對象)為不同的客戶提供通常的、直接的訪問目標對象的方式。代理對象提供和目標對象一樣的介面。代理對象負責與目標對象交互的細節,代表客戶對象與目標對象交互。所以客戶對象不再需要處理訪問目標對象的服務時的特殊需求。客戶對象通過它的介面調用代理對象,代理對象直接把這些調用依次地傳遞給目標對象。客戶對象不需要知道代理的原對象(目標對象)。代理對象隱藏了與客戶對象進行交互的對象細節,如:對象是否是遠程的、是否初始化、是否需要特殊的許可權等。換句話說,代理對象作為客戶和不可訪問的遠程對象或推遲初始化對象之間的透明橋樑。代理對象因使用的場景不同,代理的種類也不同。讓我們來快速的瀏覽一下一些代理和它們的目標。注意:表23.1列出了不同種類的代理對象,在一章中,僅討論遠程代理,其他的一些代理會在本書後面的模式中討論。
代理模式和裝飾模式
:代理模式:(1)客戶對象不能直接訪問目標對象(2)代理對象提供了對目標對象的訪問控制(在保護代理中)(3)代理對象不能再增加其他的功能。裝飾模式:(1)如果需要,客戶對象不能直接訪問目標對象。(2)裝飾對象不能控制對目標對象的訪問。(3)裝飾對象可以增加額外的功能。
代理模式和外觀模式:
代理模式:(1)代理對象代表一個單一對象。(2)客戶對象不能直接訪問目標對象。(3)代理對象提供了對於單一目標對象的訪問控制。外觀模式:(1)外觀對象代表了對象的一個子系統。(2)如果必要,客戶對象可以直接訪問子系統中的對象。(3)一個外觀對象提供了一個對子系統組件的簡單的、高層次的介面.
代理模式和責任鏈模式:
代理模式:(1)代理對象代表了一個單一的對象。(2)克輝請求首先被代理對象所接受,但是不直接被代理對象處理。(3)客戶請求總是被傳遞給目標對象。(4)假設客戶與伺服器正常工作,可以保證請求會得到響應,責任鏈模式:(1)責任鏈包括很多對象。(2)接受客戶請求的對象首先處理請求。(3)近當現在的接收者不能處理請求時,客戶請求才被傳遞給下一個對象。(4)不能保證請求會得到響應。也就是請求已經到達責任鏈尾,擔仍然沒有被處理。在Java中,遠程方法調用(RMI)充分的利用了遠程代理模式,讓我們快速的瀏覽一下遠程方法調用(RMI)的概念和遠程方法調用(RMI)通信過程應用的組件。
RMI:快速瀏覽
RMI使客戶對象像訪問本地對象一樣訪問遠程對象並調用其方法成為可能。(如圖23.1)
RMI通信機制:
一般地,客戶對象不能按通常方式直接訪問遠程對象。為了使客戶對象像訪問本地對象一樣訪問遠程對象的服務,RMIC(JavaRMI樁編譯器)生成的遠程對象的樁文件(stub)和遠程介面需要拷貝到客戶機器上。樁文件(stub)負責扮演著遠程對象的(遠程)代理的角色,負責把方法的調用傳遞給真實的遠程對象實現所在的遠程伺服器上。任何時候,客戶對象引用遠程對象,這個引用實際上是遠程對象的本地樁文件。也就是,當客戶調用遠程對象上的方法時,調用首先被本地樁實例所接受,樁再將這個調用傳遞到遠程伺服器上。在伺服器端,RMIC產生的遠程對象的框架文件(skeleton)接受這個調用。框架文件(skeleton)在伺服器端,不需要拷貝到客戶機器上。框架文件(skeleton)負責將這些調用轉發到真正的遠程對象的實現上。一旦遠程對象執行了方法,方法返回的結果將按照反方向返回給客戶。圖23.2說明瞭RMI通信的過程
RMI和遠程代理模式:
從RMI通信的討論中,可以看到樁類文件扮演著遠程對象的遠程代理的角色。它使得客戶訪問遠程對象就像訪問本地對象一樣成為可能。因此,一些使用了RMI技術的應用就已經暗含著代理模式的實現。
例子:
在討論外觀模式時,我們建立了一個簡單的客戶數據管理應用來驗證和保存輸入的客戶數據。我們的設計由分別代表不同客戶數據的三個類組成。在應用外觀模式以前,客戶AccountManager可以直接與子系統的三個用來驗證、保存客戶數據的類交互。應用外觀模式,我們定義了一個CustomFacade外觀對象代表客戶與三個子系統類交互(如圖23.3)。
- publicinterfaceCustomerIntrextendsjava.rmi.Remote{
- voidsetAddress(StringinAddress)throwsRemoteException;
- voidsetCity(StringinCity)throwsRemoteException;
- voidsetState(StringinState)throwsRemoteException;
- voidsetFName(StringinFName)throwsRemoteException;
- voidsetLName(StringinLName)throwsRemoteException;
- voidsetCardType(StringinCardType)throwsRemoteException;
- voidsetCardNumber(StringinCardNumber)
- throwsRemoteException;
- voidsetCardExpDate(StringinCardExpDate)
- throwsRemoteException;
- booleansaveCustomerData()throwsRemoteException;
- }
讓我們重新定義CustomerFacade外觀類,因為它要實現CustomerIntr遠程介面。不同的客戶對象通過CustomerIntr介面在具體類CustomerFacade上的實現與子系統對象進行交互。圖23.5展示了CustomerFacade和它實現的遠程介面CustomerIntr之間的結構和關聯。Listing23.1:CustomerFacadeClass?Revised
- publicclassCustomerFacadeextendsUnicastRemoteObject
- implementsCustomerIntr{
- privateStringaddress;
- privateStringcity;
- privateStringstate;
- privateStringcardType;
- privateStringcardNumber;
- privateStringcardExpDate;
- privateStringfname;
- privateStringlname;
- publicCustomerFacade()throwsRemoteException{
- super();
- System.out.println("Serverobjectcreated");
- }
- publicstaticvoidmain(String[]args)throwsException{
- Stringport="1099";
- Stringhost="localhost";
- //Checkforhostnameargument
- if(args.length==1){
- host=args[0];
- }
- if(args.length==2){
- port=args[1];
- }
- if(System.getSecurityManager()==null){
- System.setSecurityManager(newRMISecurityManager());
- }
- //Createaninstanceoftheserver
- CustomerFacadefacade=newCustomerFacade();
- //BinditwiththeRMIRegistry
- Naming.bind("//"+host+":"+port+"/CustomerFacade」,
- facade);
- System.out.println("ServiceBound…");
- }
- publicvoidsetAddress(StringinAddress)
- throwsRemoteException{
- address=inAddress;
- }
- publicvoidsetCity(StringinCity)
- throwsRemoteException{city=inCity;
- }
- publicvoidsetState(StringinState)
- throwsRemoteException{state=inState;
- }
- publicvoidsetFName(StringinFName)
- throwsRemoteException{fname=inFName;
- }
- publicvoidsetLName(StringinLName)
- throwsRemoteException{lname=inLName;
- }
- publicvoidsetCardType(StringinCardType)
- throwsRemoteException{
- cardType=inCardType;
- }
- publicvoidsetCardNumber(StringinCardNumber)
- throwsRemoteException{
- cardNumber=inCardNumber;
- }
- publicvoidsetCardExpDate(StringinCardExpDate)
- throwsRemoteException{
- cardExpDate=inCardExpDate;
- }
- publicbooleansaveCustomerData()throwsRemoteException{
- AddressobjAddress;
- AccountobjAccount;
- CreditCardobjCreditCard;
- /*
- clientistransparentfromthefollowing
- setofsubsystemrelatedoperations.
- */
- booleanvalidData=true;
- StringerrorMessage="";
- objAccount=newAccount(fname,lname);
- if(objAccount.isValid()==false){
- validData=false;
- errorMessage="InvalidFirstName/LastName";
- }
- objAddress=newAddress(address,city,state);
- if(objAddress.isValid()==false){
- validData=false;
- errorMessage="InvalidAddress/City/State";
- }
- objCreditCard=newCreditCard(cardType,cardNumber,
- cardExpDate);
- if(objCreditCard.isValid()==false){
- validData=false;
- errorMessage="InvalidCreditCardInfo";
- }
- if(!validData){
- System.out.println(errorMessage);
- returnfalse;
- }
- if(objAddress.save()&&objAccount.save()&&
- objCreditCard.save()){
- returntrue;
- }else{
- returnfalse;
- }
- }
- }
- …
- …
- publicvoidactionPerformed(ActionEvente){
- …
- …
- if(e.getActionCommand().equals(
- AccountManager.VALIDATE_SAVE)){
- //getinputvalues
- StringfirstName=objAccountManager.getFirstName();
- StringlastName=objAccountManager.getLastName();
- Stringaddress=objAccountManager.getAddress();
- …
- …
- try{
- //CallregistryforAddOperation
- facade=(CustomerIntr)Naming.lookup("rmi://"+
- objAccountManager.getRMIHost()+":"+
- objAccountManager.getRMIPort()+
- "/CustomerFacade");
- facade.setFName(firstName);
- facade.setLName(lastName);
- facade.setAddress(address);
- …
- …
- //Clientisnotrequiredtoaccesssubsystemcomponents.
- booleanresult=facade.saveCustomerData();
- if(result){
- validateCheckResult=
- "ValidCustomerData:DataSavedSuccessfully";
- }else{
- validateCheckResult=
- "InvalidCustomerData:DataCouldNotBeSaved";
- }
- }catch(Exceptionex){
- System.out.println(
- "Error:Pleasechecktoensurethe"+
- "remoteserverisrunning"+
- ex.getMessage());
- }
- objAccountManager.setResultDisplay(
- validateCheckResult);
- }
- }
和本地運行模式相似,AccountManager顯示了必要的接受輸入客戶數據的用戶界面,(如圖23.6)當用戶輸入數據點擊Validate&Save按鈕時,它會在RMI註冊表中通過引用名稱取得遠程對象的引用。
附加說明
:編譯和部署的說明1)在proxy/Server目錄下,編譯所有的Java文件2)在proxy/Server目錄下,執行下面的命令:RmicCustomerFacade這個命令調用RMI樁編譯器,分別創建樁類文件(stub)CustomerFacade_stub.class和框架類文件(skeleton)CustomerFacade_skel.class。3)從proxy/Server目錄拷貝下面的文件到proxy/Client目錄:CustomerIntr.classCustomerFacade_Stub.class4)在proxy/Client目錄下,編譯所有的Java文件5)開啟rmiregistrystartrmiregistry<objectRegistryPort>(Windows)rmiregistry&(Solaris)<objectRegistryPort>?這是RMI註冊表監聽的埠,默認為1099例如:startrmiregistry6)執行下面的命令:java-Djava.security.policy=<PolicyFile>CustomerFacade<RemoteRegistryHost><RemoteRegistryPort>例如:java-Djava.security.policy=java.policyCustomerFacadelocalhost1099<policyFile>?這是許可權文件的名字。這個文件中,需要指定潛在的運行系統在文件系統的位置。注意:Thejava.policy文件在伺服器目錄下是有效的。<RemoteRegistryHost>?這是註冊的遠程對象正在運行的DNS(DomainNameSystem)域命名系統的名字後者主機的IP地址。如果是在同一臺機器上,使用「localhost」。<RemoteRegistryPort>?註冊的遠程對象監聽指定的RemoteRegistryHost埠,默認為1099。6)下面的信息會在命令行中顯示:ServerobjectcreatedServicebound…7)在proxy/Client目錄下,執行下面的命令運行客戶程序。java-Djava.security.policy=<PolicyFile>AccountManager<RemoteRegistryHost><RemoteRegistryPort>例如:java-Djava.security.policy=java.policyAccountManagerlocalhost1099<policyFile>?這是許可權文件的名字。這個文件中,需要指定潛在的運行系統在文件系統的位置。注意:Thejava.policy文件在客戶目錄下是有效的。<RemoteRegistryHost>?這是註冊的遠程對象正在運行的DNS(DomainNameSystem)域命名系統的名字後者主機的IP地址。如果是在同一臺機器上,使用「localhost」。<RemoteRegistryPort>?註冊的遠程對象監聽指定的RemoteRegistryHost埠,默認為1099。附件為本文的代碼和原文:對於代碼的說明:為了把上面介紹的複雜過程簡單化,自己定義了兩個cmd文件。下載原嗎以後,1)先修改server目錄下的java.policy文件,
- grant
- {
- permissionjava.net.SocketPermission"*:1024-65535",
- "connect,accept,resolve";
- permissionjava.net.SocketPermission"*:1-1023",
- "connect,resolve";
- permissionjava.io.FilePermission"[b]C:\eclipse\workspace\AU2142\27\server\*[/b]","read,write";
- };
修改C:\eclipse\workspace\AU2142\27\server\*為你的下載目錄,一定要對應。2)再運行server目錄下的RunServer.cmd;3)再運行client目錄下的RunClient.cmd;注意:如果client和server不是在一臺機器上,要修改RunClient.cmd和RunServer.cmd;大家一看著兩個文件,就知道怎麼修改了,地球人都知道了!哈哈!
推薦閱讀: