17、Android設(shè)計(jì)模式--- 訪問者模式

一、介紹卿闹,定義

軟件系統(tǒng)中擁有一個由許多對象構(gòu)成的棒仍、比較穩(wěn)定的對象結(jié)構(gòu)泪酱,這些對象的類都擁有一個accept方法用來接受訪問者對象的訪問变骡。訪問者是一個接口,它擁有一個visit方法肢娘,這個方法對訪問的對象結(jié)構(gòu)中不同類型的元素作出不同的處理 呈础。在對象結(jié)構(gòu)的一次訪問過程中舆驶,我們遍歷整個對象結(jié)構(gòu),對每一個元素都實(shí)施accept方法而钞,在每一個元素的accept方法中會調(diào)用訪問者的visit方法沙廉,從而使訪問者得以處理對象結(jié)構(gòu)的每一個元素,我們可以針對對象結(jié)構(gòu)設(shè)計(jì)不同的訪問者類來完成不同的操作臼节,達(dá)到區(qū)別對待的效果撬陵。
封裝一些 作用于某種數(shù)據(jù)結(jié)構(gòu)中的各種元素的操作,它可以在不改變這個數(shù)據(jù)結(jié)構(gòu)的前提下定義作用于這些元素的新的操作网缝。

二巨税、使用場景

1.對象結(jié)構(gòu)比較穩(wěn)當(dāng),但經(jīng)常需要在此對象結(jié)構(gòu)上定義新的操作粉臊。
2.需要對一個對象結(jié)構(gòu)中的對象進(jìn)行很多不同的并且不相關(guān)的操作草添,而需要避免這些操作“污染”這些對象的類,也不希望在增加新操作時修改這些類扼仲。

三远寸、UML類圖

22.png

角色介紹
Visitor:接口或者抽象類,它定義了對每個元素(Element)訪問的行為屠凶,它的參數(shù)就是可以訪問的元素驰后,它的方法個數(shù)理論上與元素個數(shù)是一樣的。因此矗愧,訪問者模式要求元素的類族要穩(wěn)定倡怎,如果經(jīng)常添加、移除元素類贱枣,必然會頻繁修改Visitor接口,如果出現(xiàn)這種情況颤专,則說明不適合使用訪問者模式纽哥。
ConcreteVisitor:具體的訪問者,它需要給出對每一個元素類訪問時所產(chǎn)生的具體行為栖秕。
Element:元素接口或者抽象類春塌,它提供接受訪問者(accept)方法,其意義是指每一個元素都要可以被訪問者訪問簇捍。
ElementA只壳、ElementB:具體的元素類,它提供接受訪問方法的具體實(shí)現(xiàn)暑塑,而這個具體的實(shí)現(xiàn)吼句,通常情況下是使用訪問者提供的訪問該元素的方法。
ObjectStructure:定義當(dāng)中所提到的對象結(jié)構(gòu)事格,對象結(jié)構(gòu)是一個抽象描述惕艳,它內(nèi)部管理了元素的集合搞隐,并且可以迭代這些元素供訪問者訪問。

四远搪、簡單實(shí)現(xiàn)

年終業(yè)績考核時劣纲,員工分為工程師和經(jīng)理,評定員工的分為CEO和CTO谁鳍,CTO只關(guān)注工程師的代碼量癞季、經(jīng)理的新產(chǎn)品數(shù)量;CEO只關(guān)心工程師的KPI和經(jīng)理的KPI以及新產(chǎn)品數(shù)量倘潜,從中可以看出對不同員工關(guān)注點(diǎn)是不同的绷柒,這就需要對不同員工不同處理。

/* 員工基類
 * Staff類定義了員工的基本信息以及一個accept方法窍荧,accept方法表示接受訪問者的訪問辉巡,由子類具體實(shí)現(xiàn)。
 */
public abstract class Staff {
    public String name;
    public int kpi; //員工kpi

    public Staff(String aName) {
        this.name = aName;
        kpi = new Random().nextInt(10);
    }
    //接受Visitor的訪問
    public abstract void accept(Visitor visitor);
}

工程師和經(jīng)理類

/**
 * 工程師類中添加了獲取代碼行數(shù)的函數(shù)蕊退,而經(jīng)理類型中則添加了獲取新產(chǎn)品數(shù)量的函數(shù)郊楣,
 * 它們的職責(zé)是不一樣的,也正是它們的差異性瓤荔,才使得訪問者模式能夠發(fā)揮它是作用净蚤。
 * Staff、Engineer输硝、Manager3個類型就是對象結(jié)構(gòu)今瀑,這些類型相對穩(wěn)定,不會發(fā)生變化点把。
 */
public class Engineer extends Staff {
    public Engineer(String aName) {
        super(aName);
    }
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    //工程師這一年寫的代碼數(shù)量
    public int getCodeLines() {
        return new Random().nextInt(10*10000);
    }
}


public class Manager extends Staff {
    private int products;// 產(chǎn)品數(shù)量
    public Manager(String aName) {
        super(aName);
        products = new Random().nextInt(10);
    }
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    //一年內(nèi)做的產(chǎn)品數(shù)量
    public int getProducts() {
        return products;
    }
}
public interface Visitor {
    //訪問工程師類型
    public void visit(Engineer engineer);
    //訪問經(jīng)理類型
    public void visit(Manager manager);
}

然后將這些員工添加到一個業(yè)務(wù)報表類中橘荠,公司高層通過該報表類的showReport函數(shù)查看所有員工的業(yè)績

public class BusinessReport {
   List<Staff> mStaffs = new LinkedList<Staff>();
   public BusinessReport() {
       mStaffs.add(new Manager("manager-1"));
       mStaffs.add(new Engineer("engineer-1"));
       mStaffs.add(new Engineer("engineer-2"));
       mStaffs.add(new Engineer("engineer-3"));
       mStaffs.add(new Manager("manager-2"));
   }

   /**
    * 為訪問者展示報表
    * @param visitor CEO或者CTO
    */
   public void showReport(Visitor visitor) {
       for (Staff staff : mStaffs) {
           staff.accept(visitor);
       }
   }
}

如果不使用訪問者模式,只用一個visit函數(shù)處理郎逃,就需要在其中對不同員工類型進(jìn)行判斷哥童,然后分別處理:

class ReportUtil{
    public void visit(Staff staff){
        if(staff instanceof Manager){
            Manager mgr = (Manager)staff;
            System.out.println("經(jīng)理:"+mgr.name+",KPI:"+mgr.kpi+",新產(chǎn)品數(shù)量"+mgr.getProducts());
        }else{
            Engineer eng= (Engineer)staff;
            System.out.println("工程師:"+eng.name+",KPI:"+eng.kpi);
        }
    }
}

這就導(dǎo)致了if-else邏輯潛逃和強(qiáng)制類型轉(zhuǎn)換,難易擴(kuò)展和維護(hù)褒翰,當(dāng)類型較多時變得復(fù)雜贮懈。而使用訪問者模式,使得結(jié)構(gòu)更加清晰优训,靈活性高朵你。

在CEO的訪問者中,CEO只關(guān)注Engineer員工的KPI揣非,而對于Manager類型的員工除了KPI之外抡医,還有該Manager本年度新開發(fā)產(chǎn)品的數(shù)量。
兩類員工關(guān)注點(diǎn)不同早敬,通過兩個visitor方法分別進(jìn)行處理魂拦。

public class CEOVisitor implements Visitor {
   @Override
   public void visit(Engineer engineer) {
       Log.v(MainActivity.TAG, "engineer:" + engineer.name + ", KPI:" + engineer.kpi);
   }

   @Override
   public void visit(Manager manager) {
       Log.v(MainActivity.TAG, "mananger:" + manager.name + ", KPI:" + manager.kpi + "毛仪,新產(chǎn)品數(shù)量:" + manager.getProducts());
   }
}


public class CTOVisitor implements Visitor {
   @Override
   public void visit(Engineer engineer) {
       Log.v(MainActivity.TAG, "engineer:"+ engineer.name + ",codeslines:" + engineer.getCodeLines());
   }

   @Override
   public void visit(Manager manager) {
       Log.v(MainActivity.TAG, "manager:" + manager.name + "productsNumber:" + manager.getProducts());
   }
}

public class MainActivity extends AppCompatActivity {
    public static String TAG = "VisitorLOG";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        //構(gòu)建報表
        BusinessReport report = new BusinessReport();
        Log.v(TAG, "--------CEO visit--------------");
        //設(shè)置訪問者,這里是CEO
        report.showReport(new CEOVisitor());
        Log.v(TAG, "--------CTO visit-------------");
        //注入另一個訪問者, CTO
        report.showReport(new CTOVisitor());
    }
}

結(jié)果

V/VisitorLOG: --------CEO visit--------------
V/VisitorLOG: mananger:manager-1, KPI:0芯勘,新產(chǎn)品數(shù)量:5
V/VisitorLOG: engineer:engineer-1, KPI:3
V/VisitorLOG: engineer:engineer-2, KPI:6
V/VisitorLOG: engineer:engineer-3, KPI:7
V/VisitorLOG: mananger:manager-2, KPI:1箱靴,新產(chǎn)品數(shù)量:5
V/VisitorLOG: --------CTO visit-------------
V/VisitorLOG: manager:manager-1productsNumber:5
V/VisitorLOG: engineer:engineer-1,codeslines:62026
V/VisitorLOG: engineer:engineer-2,codeslines:34320
V/VisitorLOG: engineer:engineer-3,codeslines:53385
V/VisitorLOG: manager:manager-2productsNumber:5

五、模式的優(yōu)缺點(diǎn):

優(yōu)點(diǎn):
各角色職責(zé)分離荷愕,符合單一職責(zé)原則衡怀;
具有優(yōu)秀的擴(kuò)展性
使得數(shù)據(jù)結(jié)構(gòu)和作用于結(jié)構(gòu)上的操作解耦,使得操作集合可以獨(dú)立變化
靈活性
缺點(diǎn):
具體元素對訪問者公布細(xì)節(jié)安疗,違反了迪米特原則
具體元素變更時導(dǎo)致修改成本加大抛杨;
違反了依賴倒置原則,為了達(dá)到“區(qū)別對待”而依賴了具體類荐类,沒有依賴抽象怖现。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市玉罐,隨后出現(xiàn)的幾起案子屈嗤,更是在濱河造成了極大的恐慌,老刑警劉巖吊输,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饶号,死亡現(xiàn)場離奇詭異,居然都是意外死亡季蚂,警方通過查閱死者的電腦和手機(jī)茫船,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扭屁,“玉大人算谈,你說我怎么就攤上這事×侠模” “怎么了然眼?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長幔欧。 經(jīng)常有香客問我,道長丽声,這世上最難降的妖魔是什么礁蔗? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮雁社,結(jié)果婚禮上浴井,老公的妹妹穿的比我還像新娘。我一直安慰自己霉撵,他們只是感情好磺浙,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布洪囤。 她就那樣靜靜地躺著,像睡著了一般撕氧。 火紅的嫁衣襯著肌膚如雪瘤缩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天伦泥,我揣著相機(jī)與錄音剥啤,去河邊找鬼。 笑死不脯,一個胖子當(dāng)著我的面吹牛府怯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播防楷,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼牺丙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了复局?” 一聲冷哼從身側(cè)響起冲簿,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎肖揣,沒想到半個月后民假,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡龙优,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年羊异,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彤断。...
    茶點(diǎn)故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡野舶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宰衙,到底是詐尸還是另有隱情平道,我是刑警寧澤,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布供炼,位于F島的核電站一屋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏袋哼。R本人自食惡果不足惜冀墨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望涛贯。 院中可真熱鬧诽嘉,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至悦冀,卻和暖如春趋翻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背雏门。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工嘿歌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人茁影。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓宙帝,卻偏偏與公主長得像,于是被迫代替她去往敵國和親募闲。 傳聞我的和親對象是個殘疾皇子步脓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評論 2 354