熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java高級技術 >> 正文

visitor模式概念——visitor模式進一步

2022-06-13   來源: Java高級技術 

  訪問者模式的角色
抽象訪問者聲明一個或者多個訪問操作形成所有的具體元素都要實現的接口
具體訪問者實現抽象訪問者所聲明的接口
抽象節點聲明一個接受操作接受一個訪問者對象作為參量
具體節點實現了抽象元素所規定的接受操作
結構對象遍歷結構中的所有元素類似List Set等
在什麼情況下應當使用訪問者模式
訪問者模式應該用在被訪問類結構比較穩定的時候換言之系統很少出現增加新節點的
情況因為訪問者模式對開-閉原則的支持並不好訪問者模式允許在節點中加入方法
是傾斜的開閉原則類似抽象工廠
訪問者模式的缺點
增加節點困難
破壞了封裝
因為訪問者模式的缺點和復雜性很多設計師反對使用訪問者模式個人感覺應該在了解的
情況下考慮衡量選擇

靜態分派動態分派多分派單分派  visitor模式准備 
靜態分派:
定義發生在編譯時期分派根據靜態類型信息發生重載就是靜態分派
什麼是靜態類型變量被聲明時的類型是靜態類型
      什麼是動態類型變量所引用的對象的真實類型
有兩個類BlackCat WhiteCat都繼承自Cat
如下調用
class Cat{}
class WhiteCat extends Cat{}
class BlackCat extends Cat{}
public class Person {
    public void feed(Cat cat){
        Systemoutprintln(feed cat);
    }
    public void feed(WhiteCat cat){
        Systemoutprintln(feed WhiteCat);
    }
    public void feed(BlackCat cat){
        Systemoutprintln(feed BlackCat);
    }
    public static void main(String[] args) {
        Cat wc = new WhiteCat();
        Cat bc = new BlackCat();
        Person p = new Person();
        pfeed(wc);
        pfeed(bc);
    }

}
運行結果是:
feed cat
feed cat
這樣的結果是因為重載是靜態分派在編譯器執行的取決於變量的聲明類型因為wc bc都是Cat所以調用的都是feed(Cat cat)的函數
動態分派
定義發生在運行期動態分派動態的置換掉某個方法
還是上邊類似的例子
class Cat{
    public void eat(){
        Systemoutprintln(cat eat);
    }
}
public class BlackCat extends Cat{
    public void eat(){
        Systemoutprintln(black cat eat);
    }
    public static void main(String[] args){
        Cat cat = new BlackCat();
        cateat();
    }
}這個時候的結果是:
black cat eat
這樣的結果是因為在執行期發生了向下轉型就是動態分派了

單分派
定義根據一個宗量的類型進行方法的選擇
多分派
定義根據多於一個宗量的類型對方法的選擇
說明多分派其實是一系列的單分派組成的區別的地方就是這些但分派不能分割
C++ Java都是動態單分派靜態多分派語言
多分派的語言有CLOS  Cecil


訪問差異類型的集合類visitor模式入門
訪問差異類型的集合類visitor模式入門
本文對應代碼下載這裡
問題提出
訪問同一類型的集合類是我們最常見的事情了我們工作中這樣的代碼太常見了


  Iterator ie  =  erator();
   while (iehasNext())  {
     Person p  =  (Person)ienext();
     pdoWork();
 } 

這種訪問的特點是集合類中的對象是同一類對象Person他們擁有功能的方法run我們調用的恰好是這個共同的方法
在大部份的情況下這個是可以的但在一些復雜的情況如被訪問者的繼承結構復雜被訪問者的並不是同一類對象
也就是說不是繼承自同一個根類方法名也並不相同例如Java GUI中的事件就是一個例子
例如這樣的問題有如下類和方法:
PA 方法runPA();
PB 方法runPB();
PC 方法runPC();
PD 方法runPD();
PE 方法runPE();
有一個集合類List
List list = new ArrayList();
listadd(new PA());
listadd(new PB());
listadd(new PC());
listadd(new PD());
listadd(new PE());

  二:解決
要求能訪問到每個類的對應的方法我們第一反應應該是這樣的


   Iterator ie  =  erator();
    while (iehasNext())  {
      Object obj  =  ienext();
        if  (obj  instanceof  PA)  {
          ((PA)obj)runPA();
       } else   if (obj  instanceof  PB)  {
          ((PB)obj)runPB();
       } else   if (obj  instanceof  PC)  {
          ((PC)obj)runPC();
      } else   if (obj  instanceof  PD)  {
         ((PD)obj)runPD();
      } else   if (obj  instanceof  PE)  {
         ((PE)obj)runPE();
     } 
 } 

新問題及分析解決
當數目變多的時候維護if else是個費力氣的事情
仔細分析ifelse做的工作首先判斷類型然後根據類型執行相應的函數
如何才能解決這兩個問題呢?首先想到的是java的多態多態就是根據參數執行相應的內容
能很容易的解決第二個問題我們可以寫這樣一個類


    public   class  visitor  {
        public   void  run(PA pa)  {
          parunPA();
      } 
        public   void  run(PB pb)  {
          pbrunPB();
      } 
        public   void  run(PC pc)  {
          pcrunPC();
     } 
       public   void  run(PD pd)  {
         pdrunPD();
     } 
       public   void  run(PE pe)  {
         perunPE();
     } 
 } 

這樣只要調用run方法傳入對應的參數就能執行了
還有一個問題就是判斷類型由於重載(overloading)是靜態多分配(java語言本身是支持靜態多分配
關於這個概念請看這裡)所以造成重載只根據傳入對象的定義類型而不是實際的類型所以必須在傳入前就確定類型
這可是個難的問題因為在容器中對象全是Object出來後要是判斷是什麼類型必須用
if (xx instanceof xxx)這種方法如果用這種方法啟不是又回到了原點有沒有什麼更好的辦法呢?

我們知到Java還有另外一個特點覆寫(overriding)而覆寫是動態單分配的(關於這個概念見這裡)
那如何利用這個來實現呢?看下邊這個方法:
 我們讓上邊的一些類PA PB PC PD PE都實現一個接口P加入一個方法accept();


    public   void  accept(visitor v)  {
       // 把自己傳入 
       vrun( this );
  } 
  然後在visitor中加入一個方法
    public   void  run(P p)  {
       // 把自己傳入 
       paccept( this );
  } 
  // 這樣你在遍歷中可以這樣寫 
  Visitor v  =   new  Visitor();
 Iterator ie  =  erator();
   while (iehasNext())  {
     P p  =  (P)ienext();
         paccept(v);
     } 
 } 

首先執行的是把自己傳入在這裡由於Java的特性實際執行的是子類的accept()也就是實際類的accept
然後是把自己傳入在這裡再次把this傳入就明確類型ok我們巧妙的利用overriding解決了這個問題
其實歸納一下第二部分一個關鍵點是自己認識自己是不是很可笑
其實在計算計技術領域的很多技術上看起來很高深的東西其實就是現有社會中人的生活方式的一種映射
而且這種方式是簡單的不能再簡單的方式上邊的全部過程基本上是一個簡單的visitor模式實現visitor模式
已經是設計模式中比較復雜的模式了但其實原理簡單到你想笑看看下邊這個比喻也許你的理解會更深刻

一個幫助理解的比喻
題目指揮工人工作
條件你有個全能工人樣相同工作
需求做完工作
實現大喊一聲所有人去工作

條件變了工人不是全能但是工作相同ok問題不大
條件再變工作不是相同但工人是全能ok問題不大

以上三種情況在現實生活中是很少發生得最多的情況是這樣
個工人每人會做一種工作樣工作你又一份名單Collection)寫著誰做什麼但你不認識任何人
這個時候你怎麼指揮呢方案一
你可以一個個的叫工人然後問他們名字認識他們查名單告訴他們做什麼工作
你可以直接叫出他們名字告訴他們干什麼不需要知到他是誰
看起來很簡單但如果你要指揮萬人呢 ?而且人員是流動的每天的人不同你每天拿到一張文檔
其實很簡單最常用的做法是你把這份名單貼在牆上然後大喊一聲所有人按照去看按照自己的分配情況去做
這裡利用的關鍵點是所有工人自己認識自己你不能苛求每個工人會做所有工作不能苛求所有工作相同但你
能要求所有工人都認識自己

再想想我們開始的程序每個工人對應著PA PB PC PD PE
所有的工人都使工人P
每個工人會做的東西不一樣runPA runPB runPC
你有一份名單Visitor(重載)記錄著誰做什麼工作

看完上邊這些你是不是會產生如下的問題
問題為什麼不把這些方法的方法名做成一樣的那就可以解決了
例如我們每個PA PB PC都加入一個run 方法然後run內部再調用自己對應的runPx()方法
答案有些時候從不同的角度考慮或者因為實現的復雜度早成很難統一方法名
例如上邊指揮人工作的例子的例子其實run方法就是大叫一聲去工作因為每個工人只會做一種工作所以能行
但我們不能要求所有人只能會做一種事情這個要求很愚蠢所以如果每個工人會干兩種或者多種工作呢
也就是我PA 有runPA() walkPA()等等方法 PB有runPB() climbPB()等等
這個時候按照名單做事才是最好的辦法


From:http://tw.wingwit.com/Article/program/Java/gj/201311/27271.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.