設計模式 ( 二十 ) 訪問者模式Visitor(對象行為型)

特此說明:對訪問者模式理解不是特別透徹,若有誤,請指正,謝謝!

1.概述

在軟件開發過程中,對于系統中的某些對象,它們存儲在同一個集合collection中,且具有不同的類型,而且對于該集合中的對象,可以接受一類稱為訪問者的對象來訪問,而且不同的訪問者其訪問方式有所不同。

例子1:顧客在超市中將選擇的商品,如蘋果、圖書等放在購物車中,然后到收銀員處付款。在購物過程中,顧客需要對這些商品進行訪問,以便確認這些商品的質量,之后收銀員計算價格時也需要訪問購物車內顧客所選擇的商品。

此時,購物車作為一個ObjectStructure(對象結構)用于存儲各種類型的商品,而顧客和收銀員作為訪問這些商品的訪問者,他們需要對商品進行檢查和計價。不同類型的商品其訪問形式也可能不同,如蘋果需要過秤之后再計價,而圖書不需要。

2.問題

對同一集合對象的操作并不是唯一的,對相同的元素對象可能存在多種不同的操作方式。而且這些操作方式并不穩定,如果對需要增加新的操作,如何滿足新的業務需求?

3.解決方案

訪問者模式:表示一個作用于某對象結構中的各元素的操作。它使你可以在不改變各元素的類的前提下定義作用于這些元素的新操作。

1)訪問者模式中對象結構存儲了不同類型的元素對象,以供不同訪問者訪問。

2)訪問者模式包括兩個層次結構,一個是訪問者層次結構,提供了抽象訪問者和具體訪問者,一個是元素層次結構,提供了抽象元素和具體元素。

相同的訪問者可以以不同的方式訪問不同的元素,相同的元素可以接受不同訪問者以不同訪問方式訪問。在訪問者模式中,增加新的訪問者無須修改原有系統,系統具有較好的可擴展性

4.適用性

在下列情況下使用Vi s i t o r模式:
? 一個對象結構包含很多類對象,它們有不同的接口,而你想對這些對象實施一些依賴于其具體類的操作。
? 需要對一個對象結構中的對象進行很多不同的并且不相關的操作,而你想避免讓這些操作“污染”這些對象的類。 Visitor使得你可以將相關的操作集中起來定義在一個類中。當該對象結構被很多應用共享時,用Visitor模式讓每個應用僅包含需要用到的操作。
? 定義對象結構的類很少改變,但經常需要在此結構上定義新的操作。改變對象結構類需要重定義對所有訪問者的接口,這可能需要很大的代價。如果對象結構類經常改變,那么可能還是在這些類中定義這些操作較好。

5.結構

1.jpg

6.模式的組成

訪問者模式包含如下角色:
抽象訪問者(Vistor): — 為該對象結構中ConcreteElement的每一個類聲明一個Visit操作。該操作的名字和特
征標識了發送Visit請求給該訪問者的那個類。這使得訪問者可以確定正被訪問元素
的具體的類。這樣訪問者就可以通過該元素的特定接口直接訪問它。
具體訪問者(ConcreteVisitor): — 實現每個由Visitor聲明的操作。每個操作實現本算法的一部分,而該算法片斷乃是
對應于結構中對象的類。ConcreteVisitor為該算法提供了上下文并存儲它的局部狀態。
這一狀態常常在遍歷該結構的過程中累積結果。
 抽象元素(Element):定義一個Accept操作,它以一個訪問者為參數。
具體元素(ConcreteElement):   實現Accept操作,該操作以一個訪問者為參數。
對象結構(ObjectStructure): 能枚舉它的元素??梢蕴峁┮粋€高層的接口以允許該訪問者訪問它的元素。可以是一個復合或是一個集合,如一個列表或一個無序集合。

7.效果

訪問者模式的優點:

?使得增加新的訪問操作變得很容易。如果一些操作依賴于一個復雜的結構對象的話,那么一般而言,增加新的操作會很復雜。而使用訪問者模式,增加新的操作就意味著增加一個新的訪問者類,因此,變得很容易。

?將有關元素對象的訪問行為集中到一個訪問者對象中,而不是分散到一個個的元素類中。

?訪問者模式可以跨過幾個類的等級結構訪問屬于不同的等級結構的成員類。迭代子只能訪問屬于同一個類型等級結構的成員對象,而不能訪問屬于不同等級結構的對象。訪問者模式可以做到這一點。

?讓用戶能夠在不修改現有類層次結構的情況下,定義該類層次結構的操作。

訪問者模式的缺點:

?增加新的元素類很困難。在訪問者模式中,每增加一個新的元素類都意味著要在抽象訪問者角色中增加一個新的抽象操作,并在每一個具體訪問者類中增加相應的具體操作,違背了“開閉原則”的要求。

?破壞封裝。訪問者模式要求訪問者對象訪問并調用每一個元素對象的操作,這意味著元素對象有時候必須暴露一些自己的內部操作和內部狀態,否則無法供訪問者訪問。

8.實現

我們是了phppan提供一個例子:

<?php  
/** 
 * 訪問者模式 
 * @author guisu 
 *  
 */  
  
interface Visitor {  
    public function visitConcreteElementA(ConcreteElementA $elementA);  
    public function visitConcreteElementB(concreteElementB $elementB);  
}  
  
interface Element {  
    public function accept(Visitor $visitor);  
}  
  
/** 
 * 具體的訪問者1 
 */  
class ConcreteVisitor1 implements Visitor {  
    public function visitConcreteElementA(ConcreteElementA $elementA){  
        echo $elementA->getName(),' visitd by ConcerteVisitor1 <br />';  
    }  
  
    public function visitConcreteElementB(ConcreteElementB $elementB){  
        echo $elementB->getName().' visited by ConcerteVisitor1 <br />';  
    }  
  
}  
  
/** 
 * 具體的訪問者2 
 */  
class ConcreteVisitor2 implements Visitor {  
    public function visitConcreteElementA(ConcreteElementA $elementA){  
        echo $elementA->getName(),   ' visitd by ConcerteVisitor2 <br />';  
    }  
  
    public function visitConcreteElementB(ConcreteElementB $elementB){  
        echo $elementB->getName(), ' visited by ConcerteVisitor2 <br />';  
    }  
  
}  
  
/** 
 * 具體元素A 
 */  
class ConcreteElementA implements Element {  
    private$_name;  
  
    public function __construct($name){  
        $this->_name =$name;  
    }  
  
    public function getName(){  
        return$this->_name;  
    }  
  
    /** 
     * 接受訪問者調用它針對該元素的新方法 
     * @param Visitor $visitor 
     */  
    public function accept(Visitor $visitor){  
        $visitor->visitConcreteElementA($this);  
    }  
  
}  
  
/** 
 *  具體元素B 
 */  
class ConcreteElementB implements Element {  
    private$_name;  
  
    public function __construct($name){  
        $this->_name =$name;  
    }  
  
    public function getName(){  
        return$this->_name;  
    }  
  
    /** 
     * 接受訪問者調用它針對該元素的新方法 
     * @param Visitor $visitor 
     */  
    public function accept(Visitor $visitor){  
        $visitor->visitConcreteElementB($this);  
    }  
  
}  
  
/** 
 * 對象結構 即元素的集合 
 */  
class ObjectStructure {  
    private$_collection;  
  
    public function __construct(){  
        $this->_collection =array();  
    }  
  
  
    public function attach(Element $element){  
        returnarray_push($this->_collection,$element);  
    }  
  
    public function detach(Element $element){  
        $index=array_search($element,$this->_collection);  
        if($index!==FALSE){  
            unset($this->_collection[$index]);  
        }  
  
        return$index;  
    }  
  
    public function accept(Visitor $visitor){  
        foreach($this->_collection as$element){  
            $element->accept($visitor);  
        }  
    }  
}  
  
class Client {  
  
    /** 
     * Main program. 
     */  
    public static function main(){  
        $elementA = new ConcreteElementA("ElementA");  
        $elementB = new ConcreteElementB("ElementB");  
        $elementA2 = new ConcreteElementB("ElementA2");  
        $visitor1 = new ConcreteVisitor1();  
        $visitor2 = new ConcreteVisitor2();  
  
        $os = new ObjectStructure();  
        $os->attach($elementA);  
        $os->attach($elementB);  
        $os->attach($elementA2);  
        $os->detach($elementA);  
        $os->accept($visitor1);  
        $os->accept($visitor2);  
    }  
  
}  
  
Client::main();  
?>

9.與其他相關模式

?迭代器模式)由于訪問者模式需要對對象結構進行操作,而對象結構本身是一個元素對象的集合,因此訪問者模式經常需要與迭代器模式聯用,在對象結構中使用迭代器來遍歷元素對象。

?組合模式)在訪問者模式中,元素對象可能存在容器對象和葉子對象,因此可以結合組合模式來進行設計。

10.擴展

傾斜的“開閉原則” 

?訪問者模式以一種傾斜的方式支持“開閉原則”,增加新的訪問者方便,但是增加新的元素很困難。

面向對象的設計原則中最重要的便是所謂的"開一閉"原則。一個軟件系統的設計應當盡量做到對擴展開放,對修改關閉。達到這個原則的途徑就是遵循"對變化的封裝"的原則。這個原則講的是在進行軟件系統的設計時,應當設法找出一個軟件系統中會變化的部分,將之封裝起來。
很多系統可以按照算法和數據結構分開,也就是說一些對象含有算法,而另一些對象含有數據,接受算法的操作。如果這樣的系統有比較穩定的數據結構,又有易于變化的算法的話,使用訪問者模式就是比較合適的,因為訪問者模式使得算法操作的增加變得容易。
反過來,如果這樣一個系統的數據結構對象易于變化,經常要有新的數據對象增加進來的話,就不適合使用訪問者模式。因為在訪問者模式中增加新的節點很困難,要涉及到在抽象訪問者和所有的具體訪問者中增加新的方法。

10.總結與分析

轉自:http://blog.csdn.net/hguisu/article/details/7565000

原創文章,作者:s19930811,如若轉載,請注明出處:http://www.www58058.com/2829

(0)
s19930811s19930811
上一篇 2015-04-07
下一篇 2015-04-07

相關推薦

  • nfs 共享實驗

    nfs全稱為:network file system    網絡文件系統 在Linux里可以理解為將一個文件夾變成共享文件夾,讓其他用戶都可以訪問。而訪問的主機其本地磁盤是沒有存儲文件的 創建方法如下 :     準備兩臺機器,一臺當 client  另一臺當server    &nbsp…

    Linux干貨 2017-06-07
  • nginx 配置參數說明和實驗

    nginx.org 實驗版本: 1.10.2 相關命令: # nginx -t //檢查配置文件格式 #nginx -s reload //重新載入配置文件 實驗: 主配文件大概組成 主配置文件的設定 /etc/nginx/nginx.conf events{..} //事件驅動相關 http{..} //網站服務相關 全局配置段解讀與實驗: user ng…

    2017-05-12
  • Linux基礎學習總結(四)

    1、復制/etc/skel目錄為/home/tuser1,要求/home/tuser1及其內部文件的屬組和其它用戶均沒有任何訪問權限。 cp -r /etc/skel/ /home/tuser1chmod -R 700 /home/tuser1ll -d /home/tuser1 2、編輯/etc/group文件,添加組hadoop。 echo “hadoo…

    Linux干貨 2016-10-03
  • 磁盤管理(SWAP、dd、quota、RAID、LVM)

    2016-08-26: 授課內容: 1、SWAP交換分區的創建 2、dd命令的使用 3、設定文件系統配額 4、設定和管理軟RAID設備 5、配置邏輯卷、邏輯卷快照 1、swap (1)SWAP分區:模擬內存,當物理內存不足時,進程需要內存資源是,內存會把一部分沒有在用的進程分頁挪到硬盤的模擬內存中,騰出空間被現在需要使用內存資源的進程 即其作用是可以允許內存…

    Linux干貨 2016-09-01
  • 中秋干貨之系統啟動修復

    在使用CentOS系統時,難免會有誤操作而導致機器不能正常啟動,這里介紹了多種啟動失敗的原因和修復的方法。 grub損壞類 grub 1stage 被破壞使用dd擦寫MBR前446字節,即抹去stage1階段–[root@_2_ ~]# reboot #重啟–啟動失敗,找不到系統,這時只能借助光盤進入修復模式,重新安裝grub&#82…

    Linux干貨 2016-09-15
  • 組管理與grep匹配

    1、復制/etc/skel目錄為/home/tuser1,要求/home/tuser1及其內部文件的屬組和其它用戶均沒有任何訪問權限。     ~]# cp -r /etc/skel /home/tuser1     ~]# chmod -R 700 /home/tuser1 2…

    Linux干貨 2016-10-17
欧美性久久久久