淺談Visitor訪問者模式

1、前言html

什麼叫訪問,若是你們學過數據結構,對於這點就很清晰了,遍歷就是訪問的通常形式,單獨讀取一個元素進行相應的處理也叫做訪問,讀取到想要查看的內容+對其進行處理就叫做訪問,那麼咱們日常是怎麼訪問的呢,基本上就是直接拿着須要訪問的地址來讀寫內存就能夠了。java

爲何還要有一個訪問者模式呢,這就要放到OOP之中了,在面向對象編程的思想中,咱們使用類來組織屬性,以及對屬性的操做,那麼咱們理所固然的將訪問操做放到了類的內部,這樣看起來沒問題,可是咱們想要使用另外一種遍歷方式要怎麼辦呢,咱們必須將這個類進行修改,這在設計模式中是大忌,在設計模式中就要保證,對擴展開發,對修改關閉的開閉原則。編程

所以,咱們思考,可不能夠將訪問操做獨立出來變成一個新的類,當咱們須要增長訪問操做的時候,直接增長新的類,原來的代碼不須要任何的改變,若是能夠這樣作,那麼咱們的程序就是好的程序,所以能夠擴展,符合開閉原則。而訪問者模式就是實現這個的,使得使用不一樣的訪問方式均可以對某些元素進行訪問。設計模式

2、代碼數據結構

package designMode.visitor;

import sun.reflect.generics.visitor.Visitor;

public interface Element {
    public abstract void accept(Visitor visitor);
}
package designMode.visitor;

import designMode.iterator.Iterator;

public abstract class Entry implements Element{
    public abstract String getName();
    public abstract int getSize();
    public abstract void printList(String prefix);
    public void printList(){
        printList("");
    }
    public Entry add(Entry entry) throws RuntimeException{
        throw new RuntimeException();
    }

    public Iterator iterator() throws RuntimeException{
        throw new RuntimeException();
    }

    @Override
    public String toString() {
        return getName()+"<"+getSize()+">";
    }
}
package designMode.visitor;

import sun.reflect.generics.visitor.Visitor;

public class File extends Entry{
    private String name;
    private int size;

    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public void printList(String prefix) {
        System.out.println(prefix+"/"+this);
    }

    public void accept(Visitor visitor){
        visitor.visit(this);
    }
}
package designMode.visitor;

import sun.reflect.generics.visitor.Visitor;

import java.util.ArrayList;
import java.util.Iterator;

public class Directory extends Entry {
    String name;
    ArrayList entrys = new ArrayList();

    public Directory(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getSize() {
        int size = 0;
        Iterator it = entrys.iterator();
        while (it.hasNext()){
            size += ((Entry)it.next()).getSize();
        }
        return size;
    }

    @Override
    public Entry add(Entry entry){
        entrys.add(entry);
        return this;
    }

    public Iterator iterator(){
        return entrys.iterator();
    }

    @Override
    public void printList(String prefix) {
        System.out.println(prefix+"/"+this);
        Iterator it = entrys.iterator();
        Entry entry;
        while (it.hasNext()){
            entry = (Entry) it.next();
            entry.printList(prefix+"/"+name);
        }
    }

    public void accept(Visitor visitor){
        visitor.visit(this);
    }
}
package designMode.visitor;

public abstract class Visitor {
    public abstract void visit(File file);
    public abstract void visit(Directory directory);
}
package designMode.visitor;


import java.util.Iterator;

public class ListVisitor extends Visitor {
    String currentDir = "";
    @Override
    public void visit(File file) {
        System.out.println(currentDir+"/"+file);
    }

    @Override
    public void visit(Directory directory) {
        System.out.println(currentDir+"/"+directory);
        String saveDir = currentDir;
        currentDir +="/"+directory.getName();
        Iterator it = directory.iterator();
        while (it.hasNext()){
            Entry entry = (Entry) it.next();
            entry.accept(this);
        }
        currentDir=saveDir;
    }
}
package designMode.visitor;

import java.util.ArrayList;
import java.util.Iterator;

public class FileVisitor extends Visitor {

    String currentDir = "";
    String suffix;
    ArrayList files=new ArrayList();
    
    public FileVisitor(String suffix){
         this.suffix = suffix;
    }
    
    public void visit(File file) {
        if(file.getName().endsWith(suffix)){
         // System.out.println(currentDir+"/"+file);
            files.add(currentDir+"/"+file);
        }
    }

    public void visit(Directory directory) {
        String saveDir=currentDir;
        currentDir+=("/"+directory.getName());
        Iterator it=directory.iterator();
        while(it.hasNext()){
            Entry entry=(Entry)it.next();
            entry.accept(this);
        }
        currentDir=saveDir;
    }
    Iterator getFiles(){
        return files.iterator();
    }

}
package designMode.visitor;

import java.util.Iterator;


public class Main {

    public static void main(String[] args) {

        Directory root=new Directory("根目錄");
        
        Directory life=new Directory("個人生活");
        File eat=new File("吃火鍋.txt",100);
        File sleep=new File("睡覺.html",100);
        File study=new File("學習.txt",100);
        life.add(eat);
        life.add(sleep);
        life.add(study);
        
        Directory work=new Directory("個人工做");
        File write=new File("寫博客.doc",200);
        File paper=new File("寫論文.html",200);
        File homework=new File("寫家庭做業.docx",200);
        work.add(write);
        work.add(paper);
        work.add(homework);
        
        Directory relax=new Directory("個人休閒");
        File music=new File("聽聽音樂.js",200);
        File walk=new File("出去轉轉.psd",200);
        relax.add(music);
        relax.add(walk);
        
        Directory read=new Directory("個人閱讀");
        File book=new File("學習書籍.psd",200);
        File novel=new File("娛樂小說.txt",200);
        read.add(book);
        read.add(novel);
        
        root.add(life);
        root.add(work);
        root.add(relax);
        root.add(read);
        
        root.accept(new ListVisitor());
        System.out.println("========================");
        FileVisitor visitor=new FileVisitor(".psd");
        root.accept(visitor);
        Iterator it = visitor.getFiles();
        while(it.hasNext()){
            System.out.println(it.next());
        }
        
    }

}

運行結果第一個和使用Composite模式的結果同樣,第二個是實現另外一種方式的訪問,只訪問文件後綴爲某一特定的內容的文件,結果也是正確的,而且爲了說明咱們的訪問還能夠保存下來訪問的結果,咱們使用了ArrayList自帶的迭代器將保存到ArrayList中的結果輸出出來,咱們固然也能夠直接在遍歷的時候就輸出出來,這個看咱們的使用要求了。所以能夠看到在保證數據結構不發生變化的狀況下,能夠很是方便增長新的一種訪問方法,只須要新增長一個訪問類便可,可是若是咱們數據結構發生變化以後,就須要修改繼承自Visitor類的全部類了,這也違背了開閉原則,所以咱們應該認真考慮,到底咱們的數據結構是定死的仍是常常變化的。沒有任何一種設計模式是十全十美的,老是有所取捨,有所利弊,根據實際狀況來選擇纔是最好的設計方法。ide

這裏要說明一下雙重分發機制,咱們來看一下最核心的遍歷邏輯,結合組合模式的時候咱們已經分析過遍歷方法,遞歸,你們以爲此次咱們要怎麼在數據結構外面進行遍歷,確定還要使用遞歸,但是數據結構中的數據在類的內部,怎麼遞歸到內部呢,咱們想到了間接遞歸,也就是雙重分發。函數

public void printList(String prefix) {
        System.out.println(prefix+"/"+this);
        Iterator it=entrys.iterator();
        Entry entry;
        while(it.hasNext()){
            entry=(Entry)it.next();
            entry.printList(prefix+"/"+name);
        }
    }

上面的代碼是在組合模式類的內部遍歷的過程,能夠明確的看到遞歸(直接遞歸)的使用。咱們看一下訪問者模式中的間接遞歸:學習

public void accept(Visitor visitor) {
        //System.out.println("開始訪問文件夾:"+this);
        visitor.visit(this);
        //System.out.println("結束訪問文件夾:"+this);
        //System.out.println();
    }
public void accept(Visitor visitor) {
        //System.out.println("開始訪問文件:"+this);
        visitor.visit(this);
        //System.out.println("結束訪問文件:"+this);
        //System.out.println();
    }
public void visit(File file) {
        System.out.println(currentDir+"/"+file);
    }

    public void visit(Directory directory) {
        System.out.println(currentDir+"/"+directory);
        String saveDir=currentDir;
        currentDir+=("/"+directory.getName());
        Iterator it=directory.iterator();
        while(it.hasNext()){
            Entry entry=(Entry)it.next();
            entry.accept(this);
        }
        currentDir=saveDir;
    }

咱們看到了entry.accept(this)這句話,這句話是很是重要的,咱們在Main中是這樣用的:this

root.accept(new ListVisitor());

那麼串連起來,在Main中咱們經過Directory或者File類型的對象調用accept(訪問者)方法,接受訪問者的訪問,這是訪問者和被訪問者的第一次親密接觸,親近對方就是爲了得到對方的數據,而後才能對對方的數據進行使用,那麼怎麼拿到的呢?!咱們看到了這句visitor.visit(this);這句話無疑是重要的,被調用者告訴訪問者,我將個人內容this,所有給你了,之後訪問者就能夠對this所指代的被訪問者的內容進行操做了,分爲兩類,若是被訪問者是File文件類型的,就會直接輸出內容,到達葉子結點,訪問結束;若是是文件夾,那就很是有意思了,首先咱們仍舊是讓被訪問者將本身的內容交給訪問者visitor.visit(this);,以後public void visit(Directory directory)被調用,經過遍歷的方式將屬於這個文件夾下面的數據所有拿到Iterator it=directory.iterator();,而後開始一個個的處理,怎麼處理呢,繼續訪問屬於這個文件夾下面對象的accept()方法使用entry.accept(this);,來將訪問者交過去,交給誰?!確定是給entry所指的對象,也就是文件夾裏面的子文件夾或者文件,若是是文件的話,繼續在本身的方法中調用visitor.visit(this);,最終落實到調用 public void visit(File file)經過System.out.println(currentDir+"/"+file);訪問結束,若是不是文件呢?若爲文件夾,則繼續調用屬於文件夾的方法,就這樣不斷地往下面查找,一直到遍歷完文件夾下面的全部的元素,所以也是深度優先遍歷。就這樣經過壓棧和出棧,咱們完成了最終的遍歷,最終的出口有兩個,一個是訪問文件,輸出以後結束,另外一個是遍歷完文件夾,即便文件夾下面沒有文件依舊結束。spa

root.accept(new ListVisitor());
public void visit(File file) {
        System.out.println(currentDir+"/"+file);
    }

    public void visit(Directory directory) {
        System.out.println(currentDir+"/"+directory);
        String saveDir=currentDir;
        currentDir+=("/"+directory.getName());
        Iterator it=directory.iterator();
        while(it.hasNext()){
            Entry entry=(Entry)it.next();
            entry.accept(this);
        }
        currentDir=saveDir;
    }

在accept函數中調用visit,一樣在visit中調用accept,這就是間接遞歸,或者叫作雙重分發。產生的緣由就是訪問者須要和被訪問者相互交流,才能一步步的獲得想要的數據。咱們能夠考慮主持人採訪一個明星,那麼這個明星接受採訪,把本身基本信息(能問的問題以及某些答案)告訴主持人,問主持人有問題嗎?若是主持人有問題(還能向下問)要問那麼就再次拿着新的問題問這個明星,這個明星再次將本身關於這方面的信息告訴主持人;若是沒有問題(獲得答案),主持人將信息總結以後說出來。就這樣一直持續下去,直到主持人沒問題問了,而且明星的信息也都被問到了,這樣採訪就結束了。因而可知,不少時候設計模式都是和生活密切相關的,生活中的常識有時候就是一些套路,而這種套路就是一種抽象的模式。

3、總結

  訪問者模式是一個很是有意思的模式,由於本身須要獲得數據就須要向被訪者索取,若是可以一次索取成功,訪問就結束了,若是還須要其餘信息,則再次向被訪問者索取,就這樣知道拿到本身須要的全部數據。在本例中借用了組合模式中的數據結構,那是由於這種樹形的結構很適合咱們進行遞歸訪問。訪問者模式和迭代器模式都是在某種數據結構上進行處理,一種是對數據結構中的元素進行某種特定的處理,另外一種是用某種方式遍歷全部元素。在實際應用中,咱們根據實際須要來考慮是否是須要雙重分發機制。在本例中的訪問者模式中用到了組合模式、委託(組合)、雙重分發等原理,便於新增訪問方式,不便於對數據結構的修改。

 

淺談設計模式<最通俗易懂的講解>

相關文章
相關標籤/搜索