新聞中心
訪問(wèn)者模式,重點(diǎn)在于訪問(wèn)者二字。說(shuō)到訪問(wèn),我們腦海中必定會(huì)想起新聞訪談,兩個(gè)人面對(duì)面坐在一起。從字面上的意思理解:其實(shí)就相當(dāng)于被訪問(wèn)者(某個(gè)公眾人物)把訪問(wèn)者(記者)當(dāng)成了外人,不想你隨便動(dòng)。你想要什么,我弄好之后給你(調(diào)用你的方法)。

10年積累的網(wǎng)站建設(shè)、成都做網(wǎng)站經(jīng)驗(yàn),可以快速應(yīng)對(duì)客戶對(duì)網(wǎng)站的新想法和需求。提供各種問(wèn)題對(duì)應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識(shí)你,你也不認(rèn)識(shí)我。但先建設(shè)網(wǎng)站后付款的網(wǎng)站建設(shè)流程,更有灌南免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
01 什么是訪問(wèn)者模式?
訪問(wèn)者模式的定義如下所示,說(shuō)的是在不改變數(shù)據(jù)結(jié)構(gòu)的提前下,定義新操作。
封裝一些作用于某種數(shù)據(jù)結(jié)構(gòu)中的各元素的操作,它可以在不改變數(shù)據(jù)結(jié)構(gòu)的前提下定義作用于這些元素的新的操作。
但在實(shí)際的應(yīng)用中,我發(fā)現(xiàn)有些例子并不是如此。有些例子中并沒(méi)有穩(wěn)定的數(shù)據(jù)結(jié)構(gòu),而是穩(wěn)定的算法。在樹(shù)義看來(lái),訪問(wèn)者模式是:把不變的固定起來(lái),變化的開(kāi)放出去。
我們舉生活中一個(gè)例子來(lái)聊聊:某科學(xué)家接受記者訪談。我們都知道科學(xué)家接受訪問(wèn),肯定是有流程上的限制的,不可能讓你隨便問(wèn)。我們假設(shè)這個(gè)過(guò)程是:先問(wèn)科學(xué)家的學(xué)校經(jīng)歷,再聊你的工作經(jīng)歷,最后聊你的科研成果。那么在這個(gè)過(guò)程中,固定的是什么東西呢?固定的是接受采訪的流程。變化的是什么呢?變化的是不同的記者,針對(duì)學(xué)校經(jīng)歷,可能會(huì)提不同的問(wèn)題。
根據(jù)我們之前的理解,訪問(wèn)者模式其實(shí)就是要把不變的東西固定起來(lái),變化的開(kāi)放出去。那么對(duì)于科學(xué)家接受訪談這個(gè)事情,我們可以這么將其抽象化。
首先,我們需要有一個(gè) Visitor 類(lèi),這里定義了一些外部(記者)可以做的事情(提學(xué)校經(jīng)歷、工作經(jīng)歷、科研成就的問(wèn)題)。
- public interface Visitor {
- public void askSchoolExperience(String name);
- public void askWorkExperience(String name);
- public void askScienceAchievement(String name);
- }
接著聲明一個(gè) XinhuaVisitor 類(lèi)去實(shí)現(xiàn) Visitor 類(lèi),這表示是新華社的一個(gè)記者(訪問(wèn)者)想去訪問(wèn)科學(xué)家。
- public class XinhuaVisitor implements Visitor{
- @Override
- public void askSchoolExperience(String name) {
- System.out.printf("請(qǐng)問(wèn)%s:在學(xué)校取得的最大成就是什么?\n", name);
- }
- @Override
- public void askWorkExperience(String name) {
- System.out.printf("請(qǐng)問(wèn)%s:工作上最難忘的事情是什么?\n", name);
- }
- @Override
- public void askScienceAchievement(String name) {
- System.out.printf("請(qǐng)問(wèn)%s:最大的科研成果是什么?", name);
- }
- }
接著聲明一個(gè) Scientist 類(lèi),表明是一個(gè)科學(xué)家??茖W(xué)家通過(guò)一個(gè) accept () 方法接收記者(訪問(wèn)者)的訪問(wèn)申請(qǐng),將其存儲(chǔ)起來(lái)??茖W(xué)家定義了一個(gè) interview 方法,將訪問(wèn)的流程固定死了,只有教你問(wèn)什么的時(shí)候,我才會(huì)讓你(記者)提問(wèn)。
- public class Scientist {
- private Visitor visitor;
- private String name;
- private Scientist(){}
- public Scientist(String name) {
- this.name = name;
- }
- public void accept(Visitor visitor) {
- this.visitor = visitor;
- }
- public void interview(){
- System.out.println("------------訪問(wèn)開(kāi)始------------");
- System.out.println("---開(kāi)始聊學(xué)校經(jīng)歷---");
- visitor.askSchoolExperience(name);
- System.out.println("---開(kāi)始聊工作經(jīng)歷---");
- visitor.askWorkExperience(name);
- System.out.println("---開(kāi)始聊科研成果---");
- visitor.askScienceAchievement(name);
- }
- }
最后我們聲明一個(gè)場(chǎng)景類(lèi) Client,來(lái)模擬訪談這一過(guò)程。
- public class Client {
- public static void main(String[] args) {
- Scientist yang = new Scientist("楊振寧");
- yang.accept(new XinhuaVisitor());
- yang.interview();
- }
- }
運(yùn)行的結(jié)果為:
- ------------訪問(wèn)開(kāi)始------------
- ---開(kāi)始聊學(xué)校經(jīng)歷---
- 請(qǐng)問(wèn)楊振寧:在學(xué)校取得的最大成就是什么?
- ---開(kāi)始聊工作經(jīng)歷---
- 請(qǐng)問(wèn)楊振寧:工作上最難忘的意見(jiàn)事情是什么?
- ---開(kāi)始聊科研成果---
- 請(qǐng)問(wèn)楊振寧:最大的科研成果是什么?
看到這里,大家對(duì)于訪問(wèn)者模式的本質(zhì)有了更感性的認(rèn)識(shí)(把不變的固定起來(lái),變化的開(kāi)放出去)。在這個(gè)例子中,不變的固定的就是訪談流程,變化的就是你可以提不同的問(wèn)題。
一般來(lái)說(shuō),訪問(wèn)者模式的類(lèi)結(jié)構(gòu)如下圖所示:
- Visitor 訪問(wèn)者接口。訪問(wèn)者接口定義了訪問(wèn)者可以做的事情。這個(gè)需要你去分析哪些是可變的,將這些可變的內(nèi)容抽象成訪問(wèn)者接口的方法,開(kāi)放出去。而被訪問(wèn)者的信息,其實(shí)就是通過(guò)訪問(wèn)者的參數(shù)傳遞過(guò)去。
- ConcreteVisitor 具體訪問(wèn)者。具體訪問(wèn)者定義了具體某一類(lèi)訪問(wèn)者的實(shí)現(xiàn)。對(duì)于新華社記者來(lái)說(shuō),他們更關(guān)心楊振寧科學(xué)成果方面的事情,于是他們提問(wèn)的時(shí)候更傾向于挖掘成果。但對(duì)于青年報(bào)記者來(lái)說(shuō),他們的讀者是青少年,他們更關(guān)心楊振寧在學(xué)習(xí)、工作中的那種精神。
- Element 具體元素。這里指的是具體被訪問(wèn)的類(lèi),在我們這個(gè)例子中指的是 Scientist 類(lèi)。一般情況下,我們會(huì)提供一個(gè) accept () 方法,接收訪問(wèn)者參數(shù),將相當(dāng)于接受其范文申請(qǐng)。但這個(gè)方法也不是必須的,只要你能夠拿到 visitor 對(duì)象,你怎么定義這個(gè)參數(shù)傳遞都可以。
對(duì)于訪問(wèn)者模式來(lái)說(shuō),最重要的莫過(guò)于 Visitor、ConcreteVisitor、Element 這三個(gè)類(lèi)了。Visitor、ConcreteVisitor 定義訪問(wèn)者具體能做的事情,被訪問(wèn)者的參數(shù)通過(guò)參數(shù)傳遞給訪問(wèn)者。Element 則通過(guò)各種方法拿到被訪問(wèn)者對(duì)象,常用的是通過(guò) accept () 方法,但這并不是絕對(duì)的。
需要注意的是,我們學(xué)習(xí)設(shè)計(jì)模式重點(diǎn)是理解類(lèi)與類(lèi)之間的關(guān)系,以及他們傳遞的信息。至于是通過(guò)什么方式傳遞的,是通過(guò) accept () 方法,還是通過(guò)構(gòu)造函數(shù),都不是重點(diǎn)。
02 訪問(wèn)者模式的實(shí)際應(yīng)用
前面我們用一個(gè)生活的例子幫助大家理解訪問(wèn)者模式,相信大家對(duì)訪問(wèn)者模式應(yīng)該有了個(gè)感性的理解了。為了回歸編程實(shí)踐本身,讓大家對(duì)訪問(wèn)者模式能有更好的實(shí)踐理解。下面我們將從軟件編程上講講訪問(wèn)者模式在開(kāi)源框架中的應(yīng)用。
文件樹(shù)遍歷
JDK 中有文件操作,我們自然是清楚的。有文件操作,那自然就會(huì)有文件夾的遍歷操作,即訪問(wèn)某個(gè)文件夾下面的所有文件或文件夾。試想一下,如果我們想要打印出某個(gè)文件夾下所有文件及文件夾的名字,我們需要怎么做?
很簡(jiǎn)單的做法,其實(shí)就是直接做一個(gè)樹(shù)的遍歷,然后將名字打印出來(lái)呀!
沒(méi)錯(cuò),這確實(shí)是正確答案!
那么如果我希望統(tǒng)計(jì)一下所有文件及文件夾的個(gè)數(shù)呢?
那就再遍歷一次,然后用一個(gè)計(jì)數(shù)器去一直加一唄!
沒(méi)錯(cuò),這也是正確答案!
但你是否發(fā)現(xiàn)了這兩個(gè)過(guò)程中,我們有一個(gè)相同的操作:遍歷文件樹(shù)。無(wú)論是打印文件名,還是計(jì)算文件樹(shù),我們都需要去遍歷文件樹(shù)。而無(wú)論哪一個(gè)過(guò)程,我們最終要的其實(shí)就是訪問(wèn)文件。
還記得我們說(shuō)過(guò)設(shè)計(jì)模式的本質(zhì)是什么嗎?設(shè)計(jì)模式的本質(zhì)是找出不變的東西,再找出變化的東西,然后找到合適的數(shù)據(jù)結(jié)構(gòu)(設(shè)計(jì)模式)去承載這種變化。
在這個(gè)例子里,不變的東西是文件樹(shù)的遍歷,變化的是對(duì)于文件的不同訪問(wèn)操作。很顯然,訪問(wèn)者模式是比較適合承載這種變化的。我們可以把這種不變的東西(文件樹(shù)的遍歷)固定起來(lái),把變化的東西(文件的具體操作)開(kāi)放出去。JDK 對(duì)于文件樹(shù)的遍歷,其實(shí)就是使用訪問(wèn)者模式實(shí)現(xiàn)的。
JDK 中聲明了一個(gè) FileVisitor 接口,定義了遍歷者可以做的操作。
- public interface FileVisitor
{ - FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs);
- FileVisitResult visitFile(T file, BasicFileAttributes attrs)
- throws IOException;
- FileVisitResult visitFileFailed(T file, IOException exc)
- throws IOException;
- FileVisitResult postVisitDirectory(T dir, IOException exc)
- throws IOException;
- }
FileVisitor 中定義的 visitFile () 方法,其實(shí)就是對(duì)于文件的訪問(wèn)。被訪問(wèn)者(文件)的信息通過(guò)第一個(gè)參數(shù) file 傳遞過(guò)來(lái)。這樣遍歷者就可以訪問(wèn)文件的內(nèi)容了。
SimpleFileVisitor 則是對(duì)于 FileVisitor 接口的實(shí)現(xiàn),該類(lèi)中僅僅是做了簡(jiǎn)單的參數(shù)校驗(yàn),并沒(méi)有太過(guò)的邏輯。
- public class SimpleFileVisitor
implements FileVisitor { - @Override
- public FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs)
- throws IOException
- {
- Objects.requireNonNull(dir);
- Objects.requireNonNull(attrs);
- return FileVisitResult.CONTINUE;
- }
- @Override
- public FileVisitResult visitFile(T file, BasicFileAttributes attrs)
- throws IOException
- {
- Objects.requireNonNull(file);
- Objects.requireNonNull(attrs);
- return FileVisitResult.CONTINUE;
- }
- //....其他省略
- }
FileVisitor 類(lèi)和 SimpleFileVisitor 類(lèi)對(duì)應(yīng)的就是 UML 類(lèi)圖中的 Visitor 和 ConcreteVisitor 類(lèi)。而 Element 元素,對(duì)應(yīng)的其實(shí)是 JDK 中的 Files 類(lèi)。
Files 文件中遍歷文件樹(shù)是通過(guò) walkFileTree () 方法實(shí)現(xiàn)的。在 walkFileTree () 方法中實(shí)現(xiàn)了樹(shù)的遍歷,在遍歷到文件的時(shí)候會(huì)通過(guò) visitor 類(lèi)的 visitFile 方法調(diào)用遍歷者的方法,將遍歷到的文件傳遞給遍歷者,從而達(dá)到分離變化的目的。
ASM 修改字節(jié)碼
ASM 是 Java 的字節(jié)碼增強(qiáng)技術(shù),這里面就用到了訪問(wèn)者模式,主要是用來(lái)進(jìn)行字節(jié)碼的修改。在 ASM 中與此相關(guān)的三個(gè)類(lèi)分別是:ClassReader、ClassVisitor、ClassWriter。
ClassReader 類(lèi)相當(dāng)于訪問(wèn)者模式中的 Element 元素。它將字節(jié)數(shù)組或 class 文件讀入內(nèi)存中,并以樹(shù)的數(shù)據(jù)結(jié)構(gòu)表示。該類(lèi)定義了一個(gè) accept 方法用來(lái)和 visitor 交互。
ClassVisitor 相當(dāng)于抽象訪問(wèn)者接口。ClassReader 對(duì)象創(chuàng)建之后,需要調(diào)用 accept () 方法,傳入一個(gè) ClassVisitor 對(duì)象。在 ClassReader 的不同時(shí)期會(huì)調(diào)用 ClassVisitor 對(duì)象中不同的 visit () 方法,從而實(shí)現(xiàn)對(duì)字節(jié)碼的修改。
ClassWriter 是 ClassVisitor 的是實(shí)現(xiàn)類(lèi),它負(fù)責(zé)將修改后的字節(jié)碼輸出為字節(jié)數(shù)組。
對(duì)于 ASM 這種場(chǎng)景而言,字節(jié)碼規(guī)范是非常嚴(yán)格且穩(wěn)定的,如果隨便更改可能出問(wèn)題。但我們又需要對(duì)字節(jié)碼進(jìn)行動(dòng)態(tài)修改,從而達(dá)到某些目的。在這種情況下,ASM 的設(shè)計(jì)者采用了訪問(wèn)者模式將變化的部分隔離開(kāi)來(lái),將不變的部分固定下來(lái),從而達(dá)到了靈活擴(kuò)展的目的。
03 我們?cè)撊绾问褂?
從上面幾個(gè)例子,我們大致可以明白訪問(wèn)者模式的使用場(chǎng)景:某些較為穩(wěn)定的東西(數(shù)據(jù)結(jié)構(gòu)或算法),不想直接被改變但又想擴(kuò)展功能,這時(shí)候適合用訪問(wèn)者模式。
說(shuō)到對(duì)于訪問(wèn)者模式使用場(chǎng)景的定義,我們會(huì)覺(jué)得模板方法模式與這個(gè)使用場(chǎng)景的定義很像。但它們還是有些許差別的。訪問(wèn)者模式的變化與非變化(即訪問(wèn)者與被訪問(wèn)者)之間,它們只是簡(jiǎn)單的包含關(guān)系,而模板方法模式的變化與非變化則是繼承關(guān)系。 但它們也確實(shí)有類(lèi)似的地方,即都是封裝了固定不變的東西,開(kāi)放了變動(dòng)的東西。
訪問(wèn)者模式的優(yōu)點(diǎn)很明顯,即隔離了變化的東西,固定了不變的東西,使得整體的可維護(hù)性更強(qiáng)、具有更強(qiáng)的擴(kuò)展性。但它也帶來(lái)了設(shè)計(jì)模式通用的一些缺點(diǎn),例如:
- 類(lèi)結(jié)構(gòu)變得復(fù)雜。之前我們可是簡(jiǎn)單的調(diào)用關(guān)系,現(xiàn)在則是多個(gè)類(lèi)之間的繼承和組合關(guān)系。從一定程度上,提高了對(duì)開(kāi)發(fā)人員的要求,提高了研發(fā)成本。
- 被訪問(wèn)者的變更變得更加困難。例如我們上面科學(xué)家訪談的例子,如果科學(xué)家訪談希望新增一個(gè)環(huán)節(jié),那么 Scientist 類(lèi)需要修改,Visitor 類(lèi)、XinhuaVisitor 類(lèi)都需要修改。
有這些多優(yōu)點(diǎn),但也有這么多缺點(diǎn),那實(shí)際工作中我們應(yīng)該怎么判斷是否用訪問(wèn)者模式呢?總的原則就是揚(yáng)長(zhǎng)避短,即當(dāng)場(chǎng)景完全利用了訪問(wèn)者模式的優(yōu)點(diǎn),規(guī)避了訪問(wèn)者模式的缺點(diǎn)的時(shí)候,就是使用訪問(wèn)者模式的最佳時(shí)機(jī)。
雖然使用訪問(wèn)者模式會(huì)讓被訪問(wèn)者的變更變得更加困難,但如果被訪問(wèn)者很穩(wěn)定,基本不會(huì)變更,那這個(gè)缺點(diǎn)不就去除了么。例如在 ASM 的例子中,元素是 ClassReader,其存儲(chǔ)了字節(jié)碼的結(jié)構(gòu)。而字節(jié)碼結(jié)構(gòu)完全不會(huì)輕易改變,所以在這個(gè)「被訪問(wèn)者的變更變得更加困難」的缺點(diǎn)也就不存在了。
而「類(lèi)結(jié)構(gòu)變得復(fù)雜」這個(gè)缺點(diǎn),則是需要根據(jù)當(dāng)時(shí)業(yè)務(wù)的復(fù)雜程度來(lái)看的。如果當(dāng)時(shí)業(yè)務(wù)很簡(jiǎn)單,而且變化也不大,那么使用設(shè)計(jì)模式完全是多余的。但是如果當(dāng)時(shí)業(yè)務(wù)很復(fù)雜了,我們還是在一個(gè)類(lèi)里做修改,那么很大可能性會(huì)出大問(wèn)題。這時(shí)候就需要用設(shè)計(jì)模式來(lái)承載復(fù)雜的業(yè)務(wù)結(jié)構(gòu)了。
本文轉(zhuǎn)載自微信公眾號(hào)「陳樹(shù)義」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系陳樹(shù)義公眾號(hào)。
分享名稱(chēng):大白話聊訪問(wèn)者模式:從入門(mén)到實(shí)踐
文章URL:http://www.5511xx.com/article/dheoipd.html


咨詢(xún)
建站咨詢(xún)
