當前位置:首頁 » 密碼管理 » 訪問者模式實例

訪問者模式實例

發布時間: 2023-07-04 15:33:45

java開發中的23種設計模式詳解(轉)_Java開發模式

設計模式(Design Patterns)

——可復用面向對象軟體的基礎

設計模式(Design pattern)是一套被反復使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。

毫無疑問,設計模式於己於他人於系統都是多贏的,設計模式使代碼編制真正工程猜模喚化,設計模式是軟體工程的基石,如同大廈的一塊塊磚石一樣。項目中合理的運用設計模式可以完美的解決很多問題,每種模式在現在中都有相應的原理來與之對應,每一個模式描述了一個在我們周圍不斷重復發生的問題,以及該問題的核心解決方案,這也是它能被廣泛應用的原因。

一、設計模式的分類

總體來說設計模式分為三大類:

創建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。

結構型模式,共七種:適配器模式、裝飾器模式、代理模式、碼敬外觀模式、橋接模式、組合模式、享元模式。

行為型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、解釋器模式。

其實還有兩類:並發型模式和線程池模式。用一個圖片來整體描述一下:

二、設計模式的六大原則

1、開閉原則(Open Close Principle)

開閉原則就是說對擴展開放,對修改關閉。在程序需要進行拓展的時候,不能去修改原有的代碼,實現一個熱插拔的效果。所以一句話概括就是:為了使程序的擴展性好,易於維護和升級。想要達到這樣的效果,我們需要使用介面和抽象類,後面的具體設計中我們會提到這點。

2、里氏代換原則(Liskov Substitution Principle)

里氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。

里氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。

LSP是繼承復用的基石,只有當衍生類可以替換掉基類,軟體單位的功能不受到影響時,基類才能真正被復用,而衍生類也能夠在基類的基礎上增加新的行為。里氏代換原則是對「開-閉」原則的補充。實現「開-閉」原則的關鍵步驟就是抽象化。而基類與子類的繼承關系就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規范。—— From Bai 網路

3、依賴倒轉原則(Dependence Inversion Principle)

這個是開閉原則的基礎,具體內容:真對介面編程,依賴於抽象而不依賴於具體。

4、介面隔離原則(Interface Segregation Principle)

這個原則的意思是:使用多個隔離的介面,比使用單個介面要好。還是一個降低類之間的耦合度的意思,從這兒我們看出,其實設計模式就是一個軟體的設計思想,從大型軟體架構出發,為了升級和維護方便。所以上文中多次出現:降低依賴,降低耦合。

5、迪米特法則(最少知道原則)(Demeter Principle)

為什麼叫最少知道原則,就是說:一個實體應當盡量穗凱少的與其他實體之間發生相互作用,使得系統功能模塊相對獨立。

6、合成復用原則(Composite Reuse Principle)

原則是盡量使用合成/聚合的方式,而不是使用繼承。

三、Java的23中設計模式

從這一塊開始,我們詳細介紹Java中23種設計模式的概念,應用場景等情況,並結合他們的特點及設計模式的原則進行分析。

1、工廠方法模式(Factory Method)

工廠方法模式分為三種:

11、普通工廠模式,就是建立一個工廠類,對實現了同一介面的一些類進行實例的創建。首先看下關系圖:

舉例如下:(我們舉一個發送郵件和簡訊的例子)

首先,創建二者的共同介面:
[java]view plain publicinterfaceSender{publicvoidSend();}
其次,創建實現類:
[java]view plain {@OverridepublicvoidSend(){System.out.println("thisismailsender!");}} [java]view plain {@OverridepublicvoidSend(){System.out.println("thisissmssender!");}}
最後,建工廠類:
[java]view plain publicclassSendFactory{publicSenderproce(Stringtype){if("mail".equals(type)){returnnewMailSender();}elseif("sms".equals(type)){returnnewSmsSender();}else{System.out.println("請輸入正確的類型!");returnnull;}}}
我們來測試下:
publicclassFactoryTest{publicstaticvoidmain(String[]args){SendFactoryfactory=newSendFactory();Sendersender=factory.proce("sms");sender.Send();}}
輸出:this is sms sender!

22、多個工廠方法模式,是對普通工廠方法模式的改進,在普通工廠方法模式中,如果傳遞的字元串出錯,則不能正確創建對象,而多個工廠方法模式是提供多個工廠方法,分別創建對象。關系圖:

將上面的代碼做下修改,改動下SendFactory類就行,如下:
[java]view plainpublicclassSendFactory{publicSenderproceMail(){ returnnewMailSender();}publicSenderproceSms(){returnnewSmsSender();}}
測試類如下:
[java]view plain publicclassFactoryTest{publicstaticvoidmain(String[]args){SendFactoryfactory=newSendFactory();Sendersender=factory.proceMail();sender.Send();}}
輸出:this is mailsender!

33、靜態工廠方法模式,將上面的多個工廠方法模式里的方法置為靜態的,不需要創建實例,直接調用即可。
[java]view plain publicclassSendFactory{publicstaticSenderproceMail(){returnnewMailSender();}publicstaticSenderproceSms(){returnnewSmsSender();}} [java]view plain publicclassFactoryTest{publicstaticvoidmain(String[]args){Sendersender=SendFactory.proceMail();sender.Send();}}
輸出:this is mailsender!

總體來說,工廠模式適合:凡是出現了大量的產品需要創建,並且具有共同的介面時,可以通過工廠方法模式進行創建。在以上的三種模式中,第一種如果傳入的字元串有誤,不能正確創建對象,第三種相對於第二種,不需要實例化工廠類,所以,大多數情況下,我們會選用第三種——靜態工廠方法模式。

2、抽象工廠模式(Abstract Factory)

工廠方法模式有一個問題就是,類的創建依賴工廠類,也就是說,如果想要拓展程序,必須對工廠類進行修改,這違背了閉包原則,所以,從設計角度考慮,有一定的問題,如何解決?就用到抽象工廠模式,創建多個工廠類,這樣一旦需要增加新的功能,直接增加新的工廠類就可以了,不需要修改之前的代碼。因為抽象工廠不太好理解,我們先看看圖,然後就和代碼,就比較容易理解。

請看例子:
[java]view plain publicinterfaceSender{publicvoidSend();}
兩個實現類:
[java]view plain {@OverridepublicvoidSend(){System.out.println("thisismailsender!");}} [java]view plain {@OverridepublicvoidSend(){System.out.println("thisissmssender!");}}
兩個工廠類:
[java]view plain {@OverridepublicSenderproce(){returnnewMailSender();}} [java]view plain {@OverridepublicSenderproce(){returnnewSmsSender();}}
在提供一個介面:
[java]view plain publicinterfaceProvider{publicSenderproce();}
測試類:
[java]view plain publicclassTest{publicstaticvoidmain(String[]args){Providerprovider=newSendMailFactory();Sendersender=provider.proce();sender.Send();}}
其實這個模式的好處就是,如果你現在想增加一個功能:發及時信息,則只需做一個實現類,實現Sender介面,同時做一個工廠類,實現Provider介面,就OK了,無需去改動現成的代碼。這樣做,拓展性較好!

3、單例模式(Singleton)

單例對象(Singleton)是一種常用的設計模式。在Java應用中,單例對象能保證在一個JVM中,該對象只有一個實例存在。這樣的模式有幾個好處:

1、某些類創建比較頻繁,對於一些大型的對象,這是一筆很大的系統開銷。

2、省去了new操作符,降低了系統內存的使用頻率,減輕GC壓力。

3、有些類如交易所的核心交易引擎,控制著交易流程,如果該類可以創建多個的話,系統完全亂了。(比如一個軍隊出現了多個司令員同時指揮,肯定會亂成一團),所以只有使用單例模式,才能保證核心交易伺服器獨立控制整個流程。

首先我們寫一個簡單的單例類:
[java]view plain publicclassSingleton{/*持有私有靜態實例,防止被引用,此處賦值為null,目的是實現延遲載入*/=null;/*私有構造方法,防止被實例化*/privateSingleton(){}/*靜態工程方法,創建實例*/(){if(instance==null){instance=newSingleton();}returninstance;}/*如果該對象被用於序列化,可以保證對象在序列化前後保持一致*/publicObjectreadResolve(){returninstance;}}
這個類可以滿足基本要求,但是,像這樣毫無線程安全保護的類,如果我們把它放入多線程的環境下,肯定就會出現問題了,如何解決?我們首先會想到對getInstance方法加synchronized關鍵字,如下:
[java]view plain (){if(instance==null){instance=newSingleton();}returninstance;}
但是,synchronized關鍵字鎖住的是這個對象,這樣的用法,在性能上會有所下降,因為每次調用getInstance(),都要對對象上鎖,事實上,只有在第一次創建對象的時候需要加鎖,之後就不需要了,所以,這個地方需要改進。我們改成下面這個:
[java]view plain (){if(instance==null){synchronized(instance){if(instance==null){instance=newSingleton();}}}returninstance;}
似乎解決了之前提到的問題,將synchronized關鍵字加在了內部,也就是說當調用的時候是不需要加鎖的,只有在instance為null,並創建對象的時候才需要加鎖,性能有一定的提升。但是,這樣的情況,還是有可能有問題的,看下面的情況:在Java指令中創建對象和賦值操作是分開進行的,也就是說instance = new Singleton();語句是分兩步執行的。但是JVM並不保證這兩個操作的先後順序,也就是說有可能JVM會為新的Singleton實例分配空間,然後直接賦值給instance成員,然後再去初始化這個Singleton實例。這樣就可能出錯了,我們以A、B兩個線程為例:

a>A、B線程同時進入了第一個if判斷

b>A首先進入synchronized塊,由於instance為null,所以它執行instance = new Singleton();

c>由於JVM內部的優化機制,JVM先畫出了一些分配給Singleton實例的空白內存,並賦值給instance成員(注意此時JVM沒有開始初始化這個實例),然後A離開了synchronized塊。

d>B進入synchronized塊,由於instance此時不是null,因此它馬上離開了synchronized塊並將結果返回給調用該方法的程序。

e>此時B線程打算使用Singleton實例,卻發現它沒有被初始化,於是錯誤發生了。

所以程序還是有可能發生錯誤,其實程序在運行過程是很復雜的,從這點我們就可以看出,尤其是在寫多線程環境下的程序更有難度,有挑戰性。我們對該程序做進一步優化:
[java]view plain {=newSingleton();}(){returnSingletonFactory.instance;}
實際情況是,單例模式使用內部類來維護單例的實現,JVM內部的機制能夠保證當一個類被載入的時候,這個類的載入過程是線程互斥的。這樣當我們第一次調用getInstance的時候,JVM能夠幫我們保證instance只被創建一次,並且會保證把賦值給instance的內存初始化完畢,這樣我們就不用擔心上面的問題。同時該方法也只會在第一次調用的時候使用互斥機制,這樣就解決了低性能問題。這樣我們暫時總結一個完美的單例模式:
[java]view plain publicclassSingleton{/*私有構造方法,防止被實例化*/privateSingleton(){}/*此處使用一個內部類來維護單例*/{=newSingleton();}/*獲取實例*/(){returnSingletonFactory.instance;}/*如果該對象被用於序列化,可以保證對象在序列化前後保持一致*/publicObjectreadResolve(){returngetInstance();}}
其實說它完美,也不一定,如果在構造函數中拋出異常,實例將永遠得不到創建,也會出錯。所以說,十分完美的東西是沒有的,我們只能根據實際情況,選擇最適合自己應用場景的實現方法。也有人這樣實現:因為我們只需要在創建類的時候進行同步,所以只要將創建和getInstance()分開,單獨為創建加synchronized關鍵字,也是可以的:
[java]view plain publicclassSingletonTest{=null;privateSingletonTest(){}(){if(instance==null){instance=newSingletonTest();}}(){if(instance==null){syncInit();}returninstance;}}
考慮性能的話,整個程序只需創建一次實例,所以性能也不會有什麼影響。

補充:採用"影子實例"的辦法為單例對象的屬性同步更新
[java]view plain publicclassSingletonTest{=null;privateVectorproperties=null;publicVectorgetProperties(){returnproperties;}privateSingletonTest(){}(){if(instance==null){instance=newSingletonTest();}}(){if(instance==null){syncInit();}returninstance;}publicvoipdateProperties(){SingletonTestshadow=newSingletonTest();properties=shadow.getProperties();}}
通過單例模式的學習告訴我們:

1、單例模式理解起來簡單,但是具體實現起來還是有一定的難度。

2、synchronized關鍵字鎖定的是對象,在用的時候,一定要在恰當的地方使用(注意需要使用鎖的對象和過程,可能有的時候並不是整個對象及整個過程都需要鎖)。

到這兒,單例模式基本已經講完了,結尾處,筆者突然想到另一個問題,就是採用類的靜態方法,實現單例模式的效果,也是可行的,此處二者有什麼不同?

首先,靜態類不能實現介面。(從類的角度說是可以的,但是那樣就破壞了靜態了。因為介面中不允許有static修飾的方法,所以即使實現了也是非靜態的)

其次,單例可以被延遲初始化,靜態類一般在第一次載入是初始化。之所以延遲載入,是因為有些類比較龐大,所以延遲載入有助於提升性能。

再次,單例類可以被繼承,他的方法可以被覆寫。但是靜態類內部方法都是static,無法被覆寫。

最後一點,單例類比較靈活,畢竟從實現上只是一個普通的Java類,只要滿足單例的基本需求,你可以在裡面隨心所欲的實現一些其它功能,但是靜態類不行。從上面這些概括中,基本可以看出二者的區別,但是,從另一方面講,我們上面最後實現的那個單例模式,內部就是用一個靜態類來實現的,所以,二者有很大的關聯,只是我們考慮問題的層面不同罷了。兩種思想的結合,才能造就出完美的解決方案,就像HashMap採用數組+鏈表來實現一樣,其實生活中很多事情都是這樣,單用不同的方法來處理問題,總是有優點也有缺點,最完美的方法是,結合各個方法的優點,才能最好的解決問題!

4、建造者模式(Builder)

工廠類模式提供的是創建單個類的模式,而建造者模式則是將各種產品集中起來進行管理,用來創建復合對象,所謂復合對象就是指某個類具有不同的屬性,其實建造者模式就是前面抽象工廠模式和最後的Test結合起來得到的。我們看一下代碼:

還和前面一樣,一個Sender介面,兩個實現類MailSender和SmsSender。最後,建造者類如下: [java]view plain publicclassBuilder{privateList list=newArrayList ();publicvoidproceMailSender(intcount){for(inti=0;i0){pos--;}returncollection.get(pos);}@OverridepublicObjectnext(){if(pos

② 在組合模式中實現訪問者(Visitor)模式

本文從一個給定的實現了組合(Composite)模式的例子開始 說明怎麼在這個數據結構上實現業務邏輯代碼 依次介紹了非面向對象的方式 在組合結構中加入方法 使用訪問者(Visitor)模式以及用改進後的訪問者(Visitor)模式來實現相同的業務邏輯代碼 並且對於每種實現分別給出了優缺點

讀者定位於具有Java程序開發和設計模式經驗的開發人員

讀者通過本文可以學到如何在組合(Composite)模式中實現各種不同的業務方法及其優缺點

組合(Composite)模式

組合模式是結構型模式中的一種 GOF的《設計模式》一書中對使用組合模式的意圖描述如下 將對象組合成樹形結構以表示 部分 整體 的層次結構 Composite使得用戶對單個對象和組合對象的使用具有一致性

組合模式應用廣泛 根據GOF中對組合模式的定義 Composite模式一般由Component介面 Leaf類和Composite類組成 現在需要對一個軟體產品管理系統的實體建模 某公司開發了一系列軟體集(SofareSet) 包含了多橘正種品牌(Brand)的軟體產品 就象IBM提供了Lotus WebsPhere等品牌 每個品牌下面又有各種產品(Proct) 如IBM的Lotus下面有Domino Server/Client產品等 建模後的類圖如下(代碼可以參見隨本文帶的附件中 包 test entity下所有的源文件)

如圖所示

( )介面SofareComponent就是對應於組合模式中的Component介面 它定義了所有類共有介面的預設行為

( )AbsSofareComposite類對應於Composite類 並且是抽象類 所有可以包含子節點的類都擴展這個類 這個類的主要功能是用來存儲子部件 實現了介面中的方法 部分可以重用的代碼寫在此類中

( )SofareSet類繼承於AbsSofareComposite類 對應於軟體集 軟體集下直接可以包含品牌(Brand) 也可以直接包含不屬於任何品牌的產品(Proct)

( )Brand類繼承於AbsSofareComposite類 對應於品牌 包含了品牌名屬性 並且用來存儲Proct類的實例

( )Proct類就是對應的Leaf類 表示葉子節點 葉子節點沒有子節點

用不同的方法實現業務邏輯

數據結構建立好之後 需要在這個數據結構上添加方法實現圓氏悔業務邏輯 比如現在的這個例子中 有這樣的需求 給定一些用戶選擇好的產品 需要計算出這些選中後軟體的總價格 下面開始介紹如何使用各種不同的方法來實現這個業務邏輯

非面向對象的編程方式

這種方式下 編程思路最簡單 遍歷SofareSet實例中的所有節點 如果遍歷到的當前對象是Proct的話就累加 否則繼續遍歷下一層直到全部遍歷完畢 代碼片斷如下

    /** *取得某個SofareComponent對象下面所有Proct的價格 *@parambrand *@return */ public double getTotalPrice(){ SofareComponenttemp=sofareComponent; double totalPrice= ; //如果傳入的實例是SofareSet的類型 if (temp instanceof SofareSet){ Iteratorit=((SofareSet)sofareComponent) getChilds() &erator(); while (it hasNext())核山{ //遍歷 temp=(SofareComponent)it next(); //如果子對象是Proct類型的 直接累加 if (temp instanceof Proct){ Proctproct=(Proct)temp; totalPrice+=proct getPrice(); } else if (temp instanceof Brand){ //如果子對象是Brand類型的 則遍歷Brand下面所有的產品並累加 Brandbrand=(Brand)temp; totalPrice+=getBrandPrice(brand); } } } else if (temp instanceof Brand){ //如果傳入的實例是SofareSet的類型 則遍歷Brand下面所有的產品並累加 totalPrice+=getBrandPrice((Brand)temp); } else if (temp instanceof Proct){ //如果子對象是Proct類型的 直接返回價格 return ((Proct)temp) getPrice(); } return totalPrice; } /** *取得某個Brand對象下面所有Proct的價格 *@parambrand *@return */ private double getBrandPrice(Brandbrand){ IteratorbrandIt=brand getChilds(erator(); double totalPrice= ; while (brandIt hasNext()){ Proctproct=(Proct)brandIt next(); totalPrice+=proct getPrice(); } return totalPrice; }

這段代碼的好處是實現業務邏輯的時候無需對前面已經定好的數據結構做改動 並且效率比較高 缺點是代碼凌亂而且頻繁使用了instanceof判斷類型和強制類型轉換 代碼的可讀性不強 如果層次多了代碼就更加混亂

面向對象的編程方式(將計算價格的方法加入數據結構中)

下面我們採用面向對象的方式 可以這么做 在介面SoftWareComponent中加入一個方法 名叫getTotalPrice 方法的聲明如下

    /** *返回該節點中所有子節點對象的價格之和 *@return */ public double getTotalPrice();
由於類Brand和SofareSet都繼承了AbsSofareComposite 我們只需在類AbsSofareComposite中實現該方法getTotalPrice方法即可 如下
    public double getTotalPrice(){ Iteratorit=erator(); double price= ; while (it hasNext()){ =(SofareComponent)it next(); //自動遞歸調用各個對象的getTotalPrice方法並累加 price+=sofareComponent getTotalPrice(); } return price; }
在Proct類中實現如下
    public double getTotalPrice(){ return price; }
在外面需要取得某個對象的總價格的時候只需這樣寫(在本文的例子 test business SofareManager中可以找到這段代碼)
    //getMockData()方法返回數據 SofareComponentdata=getMockData(); //只需直接調用data對象的getTotalPrice方法就可以返回該對象下所有proct對象的價格 double price=data getTotalPrice(); //找到某個對象後直接調用其getTotalPrice方法也可以返回總價格 price=data findSofareComponentByID( id ) getTotalPrice();

現在把業務邏輯的實現都放在了數據結構中(組合模式的結構中) 好處很明顯 每個類只管理自己相關的業務代碼的實現 跟前面舉的面向過程方式的實現方式相比 沒有了instanceof和強制類型轉換 但是不好的地方是如果需要增加新的業務方法的話就很麻煩 必須在介面SoftWareComponent中首先聲明該方法 然後在各個子類中實現並且重新編譯

使用訪問者模式

使用訪問者模式就能解決上面提到的問題 如果要經常增加或者刪除業務功能方法的話 需要頻繁地對程序進行重新實現和編譯 根據面向對象設計原則之一的SRP(單一職責原則)原則 如果一個類承擔了多於一個的職責 那麼引起該類變化的原因就會有多個 就會導致脆弱的設計 在發生變化時 原有的設計可能會遭到意想不到的破壞 下面我們引入了一個叫做Visitor的介面 該介面中定義了針對各個子類的訪問方法 如下所示

    public interface Visitor{ public void visitBrand(Brandbrand); public void visitSofareSet(SofareSetsofareSet); public void visitProct(Proctproct); }
visitBrand方法是訪問Brand對象節點的時候用的 剩下的方法依次類推 並在介面SofareComponent中增加一個方法
    public void accept(Visitorvisitor);
在SofareSet中實現介面中的accept方法 首先直接調用Visitor介面中的visitSofareSet方法 傳入的參數是本身對象 然後遞歸調用子對象的accept方法
    public void accept(Visitorvisitor){ visitor visitSofareSet( this ); Iteratorit=erator(); while (it hasNext()){ SofareComponentponent=(SofareComponent)it next(); ponent accept(visitor); } }
在Brand中實現介面中的accept方法 首先直接調用Visitor介面中的visitBrand方法 傳入的參數是本身對象 然後遞歸調用子對象的accept方法
    public void accept(Visitorvisitor){ visitor visitBrand( this ); Iteratorit=erator(); while (it hasNext()){ SofareComponentponent=(SofareComponent)it next(); ponent accept(visitor); } }
其實在上面的兩個類的實現中可以將遍歷子節點並調用其accept方法的代碼寫到父類AbsSofareComposite中的某個方法中 然後直接調用父類中的這個方法即可 這里為了解釋方便分別寫在了兩個子類中 在Proct中實現介面中的accept方法 直接調用Visitor介面的visitProct方法即可
    public void accept(Visitorvisitor){ visitor visitProct( this ); }
下面需要實現Visitor介面 類名是CaculateTotalPriceVisitor 實現了計算總價格的業務邏輯 實現代碼如下所示
    public class CaculateTotalPriceVisitor implements Visitor{ private double totalPrice; public void visitBrand(Brandbrand){ } public void visitSofareSet(SofareSetsofareSet){ } public void visitProct(Proctproct){ //每次在組合的結構中碰到Proct對象節點的時候 就會調用此方法 totalPrice+=proct getPrice(); } public double getTotalPrice(){ return totalPrice; } }
上面那段代碼中 首先在類內定義一個總價格的屬性 由於Brand和SofareSet都沒有價格 因此在實現中 只需在visitProct方法中累加totalPrice即可 在外面如果需要計算總價格的話這樣寫(在本文的例子 test business SofareManager中可以找到這段代碼)
    //建立一個新的Visitor對象 = new CaculateTotalPriceVisitor(); //將該visitor對象傳到結構中 data accept(visitor); //調用visitor對象的getTotalPrice()方法就返回了總價格 double price=visitor getTotalPrice();

下面是它的時序圖 在類SofareManager中的main方法中 調用軟體集對象(data)的accept方法 並將生成的visitor對象傳給它 accept方法開始遞歸調用各個子對象的accept方法 如果當前的對象是SofareSet的實例 則調用visitor對象visitSofareSet方法 在visitor對象中對該節點的數據進行一些處理 然後返回 依次類推 遍歷到Brand對象和Proct對象也與此類似 當前的邏輯是計算軟體產品的總價格 因此當遍歷到Proct對象的時候 取出產品的價格並且累加 最後當結構遍歷完畢後 調用visitor對象的getTotalPrice方法返回給定軟體集對象的(data)的總的價格 如果需要加入一個新的計算邏輯 只實現Visitor介面 並且將該類的實例傳給data對象的accept方法就可以實現不同的邏輯方法了

點擊小圖看大圖

我們可以看到通過訪問者模式很好地解決了如何加入新的業務代碼而無需重新改動 編譯既有代碼 但是該模式也不是沒有缺點 如果在組合模式中結構加入新的子類的話會導致介面Visitor也跟著改動 導致所有Visitor的子類都需要實現新增的方法 因此這種訪問者模式適合於結構不經常變動的情況

改進訪問者模式

前面我們說到了如何使用Visitor模式及使用該模式後的優缺點 下面舉具體的例子說明 假設現在客戶提出了一個產品集(ProctSet)的概念 隨著公司軟體版本的增多 需要將同一個版本的產品(Proct)都放到產品集(ProctSet)中 而一個品牌包含有多個產品集 因為現在組合結構中增加了一個節點 所以在Visitor介面中也必須隨之增加一個叫做visitProctSet的方法 並且會導致原有系統中所有已經實現了Visitor介面的類都需要重新實現並編譯 用Java的反射機制可以解決這個問題

使用Java的Method Reflection機制實現訪問者模式

首先我們需要改變一下Visitor介面 介面名叫做ReflectionVisitor 如下所示

    public interface ReflectionVisitor{ /** *定義了一個訪問節點的方法 *@paramsofareComposite */ public void visitSofareComposite( Object sofareComposite); }

在現在的介面的方法里 能接受任意的對象(參數是Object)

下面實現介面ReflectionVisitor 名叫ReflectionVisitorImpl 代碼如下所示

    public class ReflectionVisitorImpl implements ReflectionVisitor{ public void visitSofareComposite( Object sofareComposite){ //判斷是否是null if (sofareComposite== null ){ throw new NullPointerException ( Thevisitnodeshouldnotbenull! ); } //組裝class數組 即調用動態方法的時候參數的類型 Class []classes= new Class []{sofareComposite getClass()}; //組裝與class數組相對應的值 Object []objects= new Object []{sofareComposite}; try { //查找visit方法 Methodm=getClass() getMethod( visit classes); //調用該方法 m invoke( this objects); } catch ( NoSuchMethodException e){ //沒有找到相應的方法 System out println( : +sofareComposite getClass()); } catch ( Exception e){ //發生了別的異常 System out println( ); e printStackTrace(); } } }

這段代碼首先判斷傳入的對象是否是空指針 然後創建class數組和object數組 然後用getMethod方法取得方法名是 visit 方法的參數是 對象sofareComposite對應的類 的方法 最後調用該方法 調用該方法的時候可能會發生NoSuchMethodException異常 發生這個異常就表明它的子類或者當前類中沒有與參數中傳入相對應的visit方法

下面再來寫新版本Visitor類 擴展剛寫好的那個ReflectionVisitorImpl類 名叫 如下所示

    public class extends ReflectionVisitorImpl{ private double totalPrice; public void visit(Proctproct){ totalPrice+=proct getPrice(); } public void visit(SofareSetsofareSet){ System out println( Nopriceforsofareset ); } public double getTotalPrice(){ return totalPrice; } }
代碼中聲明了兩個visit方法(因為在類ReflectionVisitorImpl中 查找名為visit 參數與傳進去的對象匹配的的方法) 一個是給Proct的 另外一個是給SofareSet的 在這里SofareSet中並沒有價格 只需當前的對象是類Proct的實例的時候將價格累加即可 如果在組合模式的結構中增加了新的類 只需要在ReflectionVisitorImpl的擴展類中聲明一個visit方法 該方法的參數是新增加的類 對於文中的例子 只需增加下面的一個方法
    public void visit(ProctSetproctSet){ //實現的代碼 }

在組合結構的介面SofareComponent中改一下accept方法 參數是修改後的Visitor介面 如下所示

public void accept(ReflectionVisitor visitor);

由於在類SofareSet Brand和ProctSet中實現上面accept方法的代碼都一樣 因此把代碼抽象到上層共有的抽象類AbsSofareComposite中 如下所示

    public void accept(ReflectionVisitorvisitor){ visitor visitSofareComposite( this ); Iteratorit=erator(); while (it hasNext()){ SofareComponentponent=(SofareComponent)it next(); //遞歸調用子對象的accept方法 ponent accept(visitor); } }
現在如果想在外面要調用的話 代碼如下所示(在本文的例子 test business SofareManager中可以找到這段代碼)
    //建立一個新的Visitor對象 reflectionVisitor = new (); //將該visitor對象傳到結構中 data accept(reflectionVisitor); //調用visitor對象的getTotalPrice()方法就返回了總價格 double price=reflectionVisitor getTotalPrice();

另外由於沒有實現Brand類的visit方法 在組合結構遍歷到Brand的節點的時候會拋出NoSuchMethodException異常 就是沒有關於該節點方法的實現 在當前的程序中會列印出一句話

You did not implement the visit method for class:class test entity Brand

如果運行程序時發生了別的異常 請參見相應的Java API文檔

在現在的改進後的訪問者模式中 如果在組合的結構中新增或刪除節點並不會對已經實現了的Visitor產生任何影響 如果新增了業務方法 只需擴展類ReflectionVisitorImpl就可以了 因此很好地解決了訪問者模式的問題

改進訪問者模式實現與既有代碼對接

到現在為止 改進後的訪問者模式好像已經很好地解決了所有出現的問題 但是考慮到有下面的這種情況 現在需要寫一個JSP的標簽庫(TagLib) 這個標簽庫還必須具有Visitor的功能(就是需要有遍歷節點的功能) 可以將節點的內容根據需要列印到HTML頁面中 由於標簽本身需要繼承相應的類(如TagSupport) 如果繼續使用上面提供的方法將無法實現 因為Java不允許多重繼承 不過我們可以將原有ReflectionVisitorImpl的代碼再改進一下以解決這種情況 新的Visitor的實現類叫NewReflectionVisitorImpl 代碼如下所示

    public class NewReflectionVisitorImpl implements ReflectionVisitor{ //實現visit方法的類 private Object targetObject; //構造方法 傳入實現了visit方法的類 public NewReflectionVisitorImpl( Object targetObject){ if (targetObject== null ) throw new NullPointerException ( ! ); this targetObject=targetObject; } public void visitSofareComposite( Object sofareComposite){ //……與上個例子相同 try { //從目標的對象中查找visit方法 Methodm=targetObject getClass() getMethod( visit classes); //調用該方法 m invoke(targetObject objects); } catch ( NoSuchMethodException e){ //……與上個例子相同 } catch ( Exception e){ //……與上個例子相同 } } }
該類的實現與上面的實現差不多 多了一個構造函數 在該構造函數的參數中傳入實現了visit方法的類 並且維護了指向該類的一個引用 另外最重要的地方是下面的兩行代碼
    //從目標的對象中查找visit方法 Methodm=targetObject getClass() getMethod( visit classes); //調用該方法 m invoke(targetObject objects);

本來的代碼中從本身的類及其子類中查找visit方法 而現在是從維護的目標類中查找visit方法

現在需要寫Tag類 這個類擴展了TagSupport類 如下所示(為說明的方便 隨本文的例子提供了一個模擬的TagSupport類)

    public class MyTag extends TagSupport{ = null ; private double totalPrice= ; public int doEngTag(){ //創建一個visitor對象 並且將本身傳入visitor對象中 ReflectionVisitorvisitor= new NewReflectionVisitorImpl( this ); //遍歷結構 sofareComponent accept(visitor); //列印出價格 out println(totalPrice); return ; } //實現了針對Proct的visit方法 public void visit(Proctproct){ totalPrice+=proct getPrice(); } public void visit(Brandbrand){ out println(brand getId()+brand getDescription()); } //別的代碼請參見隨本文帶的源程序 …… }
如果想測試上面寫的那段代碼 (在本文的例子 test business SofareManager中可以找到這段代碼))如下所示
    //getMockData()方法返回數據 SofareComponentdata=getMockData(); MyTagmyTag= new MyTag(); myTag setSofareComponent(data); //計算總價格 並列印出來 myTag doEngTag();

可以看到通過Java的反射機制很好地解決了多重繼承的問題 使該訪問者模式能夠更好地應用於你的應用中 另外可以看到 那些visit方法所在的類已經不是實現了介面ReflectionVisitor 可以說是訪問者模式在Java語言的支持下的一種特殊實現

如果擔心引入類反射機制後帶來的效率問題 你可以將Method對象通過某種方式緩沖起來 這樣不會每次從傳入的對象中找visit方法 可以部分地提高效率

結論

lishixin/Article/program/Java/gj/201311/11152

③ 訪問者模式的特點

訪問者模式把數據結構和作用於結構上的操作解耦合,使得操作集合可相對自由地演化。
訪問者模式適用於數據結構相對穩定演算法又易變化的系統。因為訪問者模式使得演算法操作增加變得容易。若系統數據結構對象易於變化,經常有新的數據對象增加進來,則不適合使用訪問者模式。
訪問者模式的優點是增加操作很容易,因為增加操作意味著增加新的訪問者。訪問者模式將有關行為集中到一個訪問者對象中,其改變不影響系統數據結構。其缺點就是增加新的數據結構很困難。

④ java中常用的設計模式有哪些

1.單例模式(有的書上說叫單態模式其實都一樣)
該模式主要目的是使內存中保持1個對象
2.工廠模式
該模式主要功能是統一提供實例對象的引用。看下面的例子:
public class Factory{
public ClassesDao getClassesDao(){
ClassesDao cd = new ClassesDaoImpl();
return cd;
}
}
interface ClassesDao{
public String getClassesName();
}
class ClassesDaoImpl implements ClassesDao {
public String getClassesName(){
System.out.println("A班");
}
}
class test
{
public static void main(String[] args){
Factory f = new Factory();
f.getClassesDao().getClassesName();
}
}
這個是最簡單的例子了,就是通過工廠方法通過介面獲取對象的引用
3.建造模式
該模式其實就是說,一個對象的組成可能有很多其他的對象一起組成的,比如說,一個對象的實現非常復雜,有很多的屬性,而這些屬性又是其他對象的引用,可能這些對象的引用又包括很多的對象引用。封裝這些復雜性,就可以使用建造模式。
4.門面模式
這個模式個人感覺像是Service層的一個翻版。比如Dao我們定義了很多持久化方法,我們通過Service層將Dao的原子方法組成業務邏輯,再通過方法向上層提供服務。門面模式道理其實是一樣的。
5.策略模式
這個模式是將行為的抽象,即當有幾個類有相似的方法,將其中通用的部分都提取出來,從而使擴展更容易。

⑤ 訪問者模式的優點

1、符合單一職責原則:凡是適用訪問者模式的場景中,元素類中需要封裝在訪問者中的操作必定是與元素類本身關系不大且是易變的操作,使用訪問者模式一方面符合單一職責原則,另一方面,因為被封裝的操作通常來說都是易變的,所以當發生變化時,就可以在不改變元素類本身的前提下,實現對變化部分的擴展。
2、擴展性良好:元素類可以通過接受不同的訪問者來實現對不同操作的擴展。

⑥ 訪問者模式的適用情況

1、 一個對象結構包含很多類對象,它們有不同的介面,而你想對這些對象實施一些依賴於其具體類的操作。
2、 需要對一個對象結構中的對象進行很多不同的並且不相關的操作,而你想避免讓這些操作「污染」這些對象的類。Visitor模式使得你可以將相關的操作集中起來 定義在一個類中。
3、 當該對象結構被很多應用共享時,用Visitor模式讓每個應用僅包含需要用到的操作。
4)、定義對象結構的類很少改變,但經常需要在此結構上定義新的操作。改變對象結構類需要重定義對所有訪問者的介面,這可能需要很大的代價。如果對象結構類經常改變,那麼可能還是在這些類中定義這些操作較好。

⑦ java observer模式 怎麼設計

在JDK(Java Development Kit)類庫中,開發人員使用了大量設計模式,正因為如此,我們可以在不修改JDK源碼的前提下開發出自己的應用軟體,研究JDK類庫中的模式實例也不失為學習如何使用設計模式的一個好方式。

創建型模式:
(1) 抽象工廠模式(Abstract Factory)
• Java.util.Calendar#getInstance()
• java.util.Arrays#asList()
• java.util.ResourceBundle#getBundle()
• java.NET.URL#openConnection()
• java.sql.DriverManager#getConnection()
• java.sql.Connection#createStatement()
• java.sql.Statement#executeQuery()
• java.text.NumberFormat#getInstance()
• java.lang.management.ManagementFactory (所有getXXX()方法)
• java.nio.charset.Charset#forName()
• javax.xml.parsers.DocumentBuilderFactory#newInstance()
• javax.xml.transform.TransformerFactory#newInstance()
• javax.xml.xpath.XPathFactory#newInstance()

(2) 建造者模式(Builder)
• java.lang.StringBuilder#append()
• java.lang.StringBuffer#append()
• java.nio.ByteBuffer#put() (CharBuffer, ShortBuffer, IntBuffer,LongBuffer, FloatBuffer 和DoubleBuffer與之類似)
• javax.swing.GroupLayout.Group#addComponent()
• java.sql.PreparedStatement
• java.lang.Appendable的所有實現類
(3) 工廠方法模式(Factory Method)
• java.lang.Object#toString() (在其子類中可以覆蓋該方法)
• java.lang.Class#newInstance()
• java.lang.Integer#valueOf(String) (Boolean, Byte, Character,Short, Long, Float 和 Double與之類似)
• java.lang.Class#forName()
• java.lang.reflect.Array#newInstance()
• java.lang.reflect.Constructor#newInstance()
(4) 原型模式(Prototype)
• java.lang.Object#clone() (支持淺克隆的類必須實現java.lang.Cloneable介面)
(5) 單例模式 (Singleton)
• java.lang.Runtime#getRuntime()
• java.awt.Desktop#getDesktop()

結構型模式:
(1) 適配器模式(Adapter)
•java.util.Arrays#asList()
•javax.swing.JTable(TableModel)
•java.io.InputStreamReader(InputStream)
•java.io.OutputStreamWriter(OutputStream)
•javax.xml.bind.annotation.adapters.XmlAdapter#marshal()
•javax.xml.bind.annotation.adapters.XmlAdapter#unmarshal()

(2) 橋接模式(Bridge)
• AWT (提供了抽象層映射於實際的操作系統)
•JDBC

(3) 組合模式(Composite)
•javax.swing.JComponent#add(Component)
•java.awt.Container#add(Component)
•java.util.Map#putAll(Map)
•java.util.List#addAll(Collection)
•java.util.Set#addAll(Collection)

(4) 裝飾模式(Decorator)
•java.io.BufferedInputStream(InputStream)
•java.io.DataInputStream(InputStream)
•java.io.BufferedOutputStream(OutputStream)
•java.util.zip.ZipOutputStream(OutputStream)
•java.util.Collections#checked[List|Map|Set|SortedSet|SortedMap]()

(5) 外觀模式(Facade)
•java.lang.Class
•javax.faces.webapp.FacesServlet

(6) 享元模式(Flyweight)
•java.lang.Integer#valueOf(int)
•java.lang.Boolean#valueOf(boolean)
• java.lang.Byte#valueOf(byte)
•java.lang.Character#valueOf(char)
(7) 代理模式(Proxy)
• java.lang.reflect.Proxy
•java.rmi.*

行為型模式:
(1) 職責鏈模式(Chain of Responsibility)
•java.util.logging.Logger#log()
•javax.servlet.Filter#doFilter()

(2) 命令模式(Command)
• java.lang.Runnable
• javax.swing.Action
(3) 解釋器模式(Interpreter)
• java.util.Pattern
• java.text.Normalizer
• java.text.Format
• javax.el.ELResolver
(4) 迭代器模式(Iterator)
• java.util.Iterator
• java.util.Enumeration
(5) 中介者模式(Mediator)
• java.util.Timer (所有scheleXXX()方法)
• java.util.concurrent.Executor#execute()
• java.util.concurrent.ExecutorService (invokeXXX()和submit()方法)
• java.util.concurrent.ScheledExecutorService (所有scheleXXX()方法)
•java.lang.reflect.Method#invoke()
(6) 備忘錄模式(Memento)
•java.util.Date
•java.io.Serializable
•javax.faces.component.StateHolder
(7) 觀察者模式(Observer)
•java.util.Observer/java.util.Observable
•java.util.EventListener (所有子類)
•javax.servlet.http.HttpSessionBindingListener
•javax.servlet.http.HttpSessionAttributeListener
•javax.faces.event.PhaseListener
(8) 狀態模式(State)
•java.util.Iterator
•javax.faces.lifecycle.LifeCycle#execute()
(9) 策略模式(Strategy)
• java.util.Comparator#compare()
• javax.servlet.http.HttpServlet
• javax.servlet.Filter#doFilter()

(10) 模板方法模式(Template Method)
•java.io.InputStream, java.io.OutputStream, java.io.Reader和java.io.Writer的所有非抽象方法
•java.util.AbstractList, java.util.AbstractSet和java.util.AbstractMap的所有非抽象方法
•javax.servlet.http.HttpServlet#doXXX()

(11) 訪問者模式(Visitor)
•javax.lang.model.element.AnnotationValue和AnnotationValueVisitor
•javax.lang.model.element.Element和ElementVisitor
•javax.lang.model.type.TypeMirror和TypeVisitor

熱點內容
php方法類 發布:2025-02-03 21:01:56 瀏覽:441
電腦基岩版材質包怎麼安裝到伺服器里 發布:2025-02-03 20:57:33 瀏覽:389
linux文件組 發布:2025-02-03 20:53:51 瀏覽:327
db2存儲執行變慢 發布:2025-02-03 20:42:21 瀏覽:765
滑板鞋腳本視頻 發布:2025-02-02 09:48:54 瀏覽:433
群暉怎麼玩安卓模擬器 發布:2025-02-02 09:45:23 瀏覽:558
三星安卓12彩蛋怎麼玩 發布:2025-02-02 09:44:39 瀏覽:744
電腦顯示連接伺服器錯誤 發布:2025-02-02 09:24:10 瀏覽:537
瑞芯微開發板編譯 發布:2025-02-02 09:22:54 瀏覽:147
linux虛擬機用gcc編譯時顯示錯誤 發布:2025-02-02 09:14:01 瀏覽:240