Android設計模式之——訪問者模式

一、介紹

訪問者模式是一種將數據操作與數據結構分離的設計模式,它是《設計模式》中23種設計模式中最復雜的一個,但它的使用頻率并不高,正如《設計模式》的作者GOF對訪問者模式的描述:大多數情況下,你不需要使用訪問者模式,但是當你一旦需要使用它時,那你就是真的需要它了。

訪問者模式的基本想法是,軟件系統中擁有一個由許多對象構成的、比較穩定的對象結構,這些對象的類都擁有一個accept方法用來接受訪問者對象的訪問。訪問者是一個接口,它擁有一個visit方法,這個方法對訪問到的對象結構中不同類型的元素作出不同的處理。在對象結構的一次訪問過程中,我們遍歷整個對象結構,對每一個元素都實施accept方法,在每一個元素的accept方法中會調用訪問者的visit方法,從而使訪問者得以處理對象結構的每一個元素,我們可以針對對象結構設計不同的訪問者類來完成不同的操作,達到區別對待的效果。

二、定義

封裝一些作用于某種數據結構中的各元素的操作,它可以在不改變這個數據結構的前提下定義作用于這些元素的新的操作。

三、使用場景

  • 對象結構比較穩定,但經常需要在此對象結構上定義新的操作。

  • 需要對一個對象結構中的對象進行很多不同的并且不相關的操作,而需要避免這些操作”污染“這些對象的類,也不希望在增加新操作時修改這些類。

四、訪問者模式的UML類圖

UML類圖:

這里寫圖片描述

角色介紹:

  • Visitor:接口或抽象類,定義了對每一個元素的訪問行為,參數就是可訪問的元素,方法個數理論上是個元素個數一樣的。因此,訪問者模式要求被訪問的對象結構要穩定,如果經常增刪元素,必然會導致頻繁修改Visitor接口,就不適合用訪問者模式了。

  • ConcreteVisitor:具體的訪問者,定義具體的對每一個元素的具體訪問行為。

  • Element:抽象的元素接口或抽象類,定義了一個接待訪問者的方法,讓每個元素都可以被訪問者訪問。

  • ElementA,ElementB:具體的元素類,提供接收訪問方法的具體實現。這個具體實現通常是調用訪問者提供的訪問該元素的方法。

  • ObjectStructure:定義對象結構,里面維護了一個元素的集合,并且迭代這些元素供訪問者訪問。

五、簡單示例

情景:年終了,公司會給員工進行業績考核。但是,不同領域的管理人員對于員工的評定標準不一樣。現在員工有工程師和經理,評定者有CEO和CTO,我們假定CTO只關注工程師的代碼量、經理的新產品數量,而CEO關注的是工程師的KPI和經理的KPI以及新產品數量。

員工基類:

/*** 員工基類(Element) */
public abstract class Staff {//員工姓名public String name;//員工KPIpublic int kpi;public Staff(String name) {super();this.name = name;this.kpi = new Random().nextInt(10);}//接受Visitor的訪問public abstract void accept(Visitor visitor);}

工程師:

/*** 工程師 */
public class Engineer extends Staff{private int codeLines;//代碼數量public Engineer(String name) {super(name);codeLines = new Random().nextInt(10 * 10000);}@Overridepublic void accept(Visitor visitor) {visitor.visit(this);}//工程師這一年寫的代碼數量public int getCodeLines(){return codeLines;}
}

經理:

/*** 經理*/
public class Manager extends Staff{private int products;//產品數量public Manager(String name) {super(name);products = new Random().nextInt(10);}@Overridepublic void accept(Visitor visitor) {visitor.visit(this);}//一年內做的產品數量public int getProducts(){return products;}
}

Visitor類:

public interface Visitor {/*** 訪問工程師類型*/public void visit(Engineer engineer);/*** 訪問經理類型*/public void visit(Manager manager);
}

CEO訪問者:

public class CEOVisitor implements Visitor {@Overridepublic void visit(Engineer engineer) {System.out.println("工程師:" + engineer.name + ", KPI:" + engineer.kpi);}@Overridepublic void visit(Manager manager) {System.out.println("經理:" + manager.name + ", KPI:" + manager.kpi+ ", 新產品數量 :" + manager.getProducts());}}

CTO訪問者:

public class CTOVisitor implements Visitor {@Overridepublic void visit(Engineer engineer) {System.out.println("工程師:" + engineer.name + ", 代碼數量:" + engineer.getCodeLines());}@Overridepublic void visit(Manager manager) {System.out.println("經理:" + manager.name +", 產品數量 :" + manager.getProducts());}}

員工報表:

//員工業務報表類(ObjectStructure)
public class BusinessReport {List<Staff> mStaffs = new LinkedList<Staff>();public BusinessReport() {mStaffs.add(new Manager("王經理"));mStaffs.add(new Engineer("工程師-A"));mStaffs.add(new Engineer("工程師-B"));mStaffs.add(new Manager("李經理"));mStaffs.add(new Engineer("工程師-C"));}/*** 為訪問者展示報表 * @param visitor 如CEO、CTO*/public void showReport(Visitor visitor){for(Staff staff : mStaffs){staff.accept(visitor);}}
}

Client訪問:

public class Client {public static void main(String[] args) {//構建報表BusinessReport report = new BusinessReport();System.out.println("===== 給CEO看報表 =====");//設置訪問者CEOreport.showReport(new CEOVisitor());System.out.println("===== 給CTO看報表 =====");//設置訪問者CTOreport.showReport(new CTOVisitor());}
}

結果:

===== 給CEO看報表 =====
經理:王經理, KPI:2, 新產品數量 :5
工程師:工程師-A, KPI:5
工程師:工程師-B, KPI:7
經理:李經理, KPI:9, 新產品數量 :8
工程師:工程師-C, KPI:1
===== 給CTO看報表 =====
經理:王經理, 產品數量 :5
工程師:工程師-A, 代碼數量:26238
工程師:工程師-B, 代碼數量:8282
經理:李經理, 產品數量 :8
工程師:工程師-C, 代碼數量:47927

從上面代碼中可以看出,如果要增加一個訪問者,你新創建一個實現了Visitor接口的類,然后實現兩個visit方法來對不同的元素進行不同的操作,從而達到數據對象與數據操作相分離的效果。如果不使用訪問者模式,而又想對不同元素進行不同的操作,那么必定會使用if-else和類型轉換,這使得代碼難以升級維護。

六、Android中的訪問者模式

安卓中的著名開源庫ButterKnife、Dagger、Retrofit都是基于APT(Annotation Processing Tools)實現。而編譯注解核心依賴APT。當我們通過APT處理注解時,最終會將獲取到的元素轉換為相應的Element元素,以便獲取到它們對應信息。那么元素基類的源碼如下:(路徑:javax.lang.model.element.Element)

public interface Element extends javax.lang.model.AnnotatedConstruct {/*** Returns the {@code kind} of this element.** @return the kind of this element*/ElementKind getKind();//獲取元素類型//代碼省略/*** Applies a visitor to this element.** @param <R> the return type of the visitor's methods* @param <P> the type of the additional parameter to the visitor's methods* @param v   the visitor operating on this element* @param p   additional parameter to the visitor* @return a visitor-specified result*/<R, P> R accept(ElementVisitor<R, P> v, P p);//接受訪問者的訪問
}

ElementVisitor就是訪問者類型,ElementVisitor源碼如下:

public interface ElementVisitor<R, P> {/*** Visits an element.* @param e  the element to visit* @param p  a visitor-specified parameter* @return a visitor-specified result*/R visit(Element e, P p);/*** A convenience method equivalent to {@code v.visit(e, null)}.* @param e  the element to visit* @return a visitor-specified result*/R visit(Element e);/*** Visits a package element.* @param e  the element to visit* @param p  a visitor-specified parameter* @return a visitor-specified result*/R visitPackage(PackageElement e, P p);/*** Visits a type element.* @param e  the element to visit* @param p  a visitor-specified parameter* @return a visitor-specified result*/R visitType(TypeElement e, P p);/*** Visits a variable element.* @param e  the element to visit* @param p  a visitor-specified parameter* @return a visitor-specified result*/R visitVariable(VariableElement e, P p);/*** Visits an executable element.* @param e  the element to visit* @param p  a visitor-specified parameter* @return a visitor-specified result*/R visitExecutable(ExecutableElement e, P p);/*** Visits a type parameter element.* @param e  the element to visit* @param p  a visitor-specified parameter* @return a visitor-specified result*/R visitTypeParameter(TypeParameterElement e, P p);/*** Visits an unknown kind of element.* This can occur if the language evolves and new kinds* of elements are added to the {@code Element} hierarchy.** @param e  the element to visit* @param p  a visitor-specified parameter* @return a visitor-specified result* @throws UnknownElementException*  a visitor implementation may optionally throw this exception*/R visitUnknown(Element e, P p);
}

在ElementVisitor中定義了多種visit接口,每個接口處理一種元素類型,那么這就是典型的訪問者模式。

七、總結

正如本節開頭引用GOF的話所說:大多數情況下,你不需要使用訪問者模式,但是,當你一旦需要使用它時,那你就是真的需要它了。在現實情況下,我們要根據具體的情況來評估是否適合使用訪問者模式,例如,我們的對象結構是否足夠穩定,使用訪問者模式是否能夠優化我們的代碼,而不是使我們的代碼變得更復雜。在使用一個模式之前,我們應該明確它的使用場景、它能解決什么問題等,以此來避免濫用設計模式的現象。

優點:

  • 各角色職責分離,符合單一職責原則。

  • 具有優秀的擴展性。

  • 使得數據結構和作用于結構上的操作解耦,使得操作集合可以獨立變化。

  • 靈活性。

缺點:

  • 具體元素對訪問者公布細節,違反了迪米特原則。

  • 具體元素變更時導致修改成本大。

  • 違反了依賴倒置原則,為了達到“區別對待”而依賴了具體類,沒有依賴抽象。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/447004.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/447004.shtml
英文地址,請注明出處:http://en.pswp.cn/news/447004.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C++類模板template <class T>簡單使用方法

一個簡單的例子 兩個數比大小 如果兩個數都是int類型 class Compare_int { public :Compare(int a,int b){xa;yb;}int max( ){return (x>y)?x:y;}int min( ){return (x<y)?x:y;} private :int x,y; }; 如果兩個數是float類型 class Compare_float { public :Compare(…

Android設計模式之——中介者模式

一、介紹 中介者模式&#xff08;Mediator Pattern&#xff09;也稱為調解者模式或調停者模式&#xff0c;Mediator本身就有調停者和調解者的意思。 在日常生活中調停者或調解者這個角色我們見得比較多的是“和事老”&#xff0c;也就是說調解兩個有爭端的人的角色&#xff0…

C++智能指針中unique_ptr部分內容的講解

參考鏈接 std::unique_ptr 介紹 定義位于頭文件<memory>std::unique_ptr 是通過指針占有并管理另一對象&#xff0c;并在 unique_ptr 離開作用域時釋放該對象的智能指針。 在下列兩者之一發生時用關聯的刪除器釋放對象&#xff1a;1&#xff0c;銷毀了管理的 unique_pt…

Java基礎——Java多線程中sleep()、wait()和notify()

一、sleep()sleep()方法源碼&#xff1a;/** * Causes the currently executing thread to sleep (temporarily cease * execution) for the specified number of milliseconds, subject to * the precision and accuracy of system timers and schedulers. The thread * does …

Key_handle的學習

代碼 一切盡在不言中 #pragma once#include "common/common.h" #include "sdf/sdf.h"#include <memory>namespace sdf {namespace algorithm {class KeyHandle {public:using erased_internal_data_t char; //使用erased_internal_data_t等效于ch…

Java基礎——虛擬機結構

一、Java平臺結構圖二、JVM、JRE和JDK關系JVM&#xff1a;Java Virtual Machine&#xff08;Java虛擬機&#xff09;&#xff0c;負責執行符合規范的Class文件 JRE&#xff1a; Java Runtime Environment &#xff08;java運行環境&#xff09;&#xff0c;包含JVM和類庫 JDK&a…

解決 SSH Connection closed by foreign host 問題

用 Xshell 連接服務器總是報錯 : Connection closed by foreign host.Disconnected from remote host... 原因可能是 SSH 服務器沒設置保活時間間隔 , 具體設置如下 : 操作 # vim /etc/ssh/sshd_config 添加兩行 , 或去掉注釋 : ClientAliveInterval 60ClientAliveCountMax…

Java基礎——synchronized

synchronized重要&#xff01;重要&#xff01;重要&#xff01;重要的事情說三遍&#xff0c;一定要記下來哦。 Java語言的關鍵字&#xff0c;當它用來修飾一個方法或者一個代碼塊的時候&#xff0c;能夠保證在同一時刻最多只有一個線程執行該段代碼。一、當兩個并發線程訪問同…

C++:MAC安裝Boost庫文件并且使用CLion開發

boost的filestem庫 C在17版本的標準庫中引入了一個filesystem庫&#xff0c;用來處理文件路徑&#xff0c;以及文件訪問。很多編譯器對filesystem庫的支持還不是很好。為了解決這個問題&#xff0c;可以臨時使用boost::filesystem來替代。其實C17標準中的filesystem庫就是從bo…

Java基礎——Java異常處理機制

一、引言 try…catch…finally恐怕是大家再熟悉不過的語句了&#xff0c;而且感覺用起來也是很簡單&#xff0c;邏輯上似乎也是很容易理解。不過&#xff0c;我親自體驗的“教訓”告訴我&#xff0c;這個東西可不是想象中的那么簡單、聽話。不信&#xff1f;那你看看下面的代碼…

clion在使用sqlite3的時候,顯示Undefined symbols for architecture x86_64錯誤的解決辦法

顯示Undefined symbols for architecture x86_64錯誤的原因 1、缺少靜態庫 環境&#xff1a;在模擬器上報錯但在真機上能運行成功&#xff0c;而且報的錯誤來自于第三方庫。原因&#xff1a;architecture x86_64 是指模擬器的架構&#xff0c;意思就是 Crypto 變量在模擬器架…

Java基礎——Java反射機制及IoC原理

一、概念 主要是指程序可以訪問&#xff0c;檢測和修改它本身狀態或行為的一種能力&#xff0c;并能根據自身行為的狀態和結果&#xff0c;調整或修改應用所描述行為的狀態和相關的語義。在java中&#xff0c;只要給定類的名字&#xff0c; 那么就可以通過反射機制來獲得類的所…

Ubuntu boost庫文件安裝編譯

簡單介紹 Boost庫是為C語言標準庫提供擴展的一些C程序庫的總稱&#xff0c;由Boost社區組織開發、維護.Boost向來有準標準庫之稱&#xff0c;很多新特性例如智能指針等都是先在boost中實現&#xff0c;后來被吸收到標準庫之中. Boost實現了日志、算法、日期、地理、數學、線程…

Java基礎——類加載機制及原理

一、什么是類的加載&#xff1f; 類的加載指的是將類的.class文件中的二進制數據讀入到內存中&#xff0c;將其放在運行時數據區的方法區內&#xff0c;然后在堆區創建一個java.lang.Class對象&#xff0c;用來封裝類在方法區內的數據結構。類的加載的最終產品是位于堆區中的Cl…

在Ubuntu環境下使用vcpkg安裝sqlite_orm包文件

Ubuntu安裝vcpkg 從github下載vcpkg的安裝包&#xff0c;在usr/local路徑下面執行如下命令 git clone https://github.com/Microsoft/vcpkg.git cd vcpkg //進入源碼目錄 ./bootstrap-vcpkg.sh //執行./bootstrap-vcpkg.sh進行編譯安裝&#xff0c;這個過程很慢 編譯安裝好…

finally語句與return語句的執行順序

網上有很多人探討Java中異常捕獲機制try...catch...finally塊中的finally語句是不是一定會被執行&#xff1f;很多人都說不是&#xff0c;當然他們的回答是正確的&#xff0c;經過我試驗&#xff0c;至少有兩種情況下finally語句是不會被執行的&#xff1a; try語句沒有被執行到…

window電腦查看ssh公鑰,以及將自己的公鑰添加到Github等類似網站

查看本機的ssh公鑰 使用命令 cd ~/.ssh使用命令 ls 可以看到 id_rsa id_rsa.pub known_hosts 三個文件&#xff0c;此處需要的是id_rsa.pub文件使用命令 cat id_rsa.pub 查看文件的內容拷貝這段內容 添加自己的公鑰 進入賬戶的設置頁面參照如下步驟&#xff0c;進入SSH Key…

java八大排序算法

一、概述 排序有內部排序和外部排序&#xff0c;內部排序是數據記錄在內存中進行排序&#xff0c;而外部排序是因排序的數據很大&#xff0c;一次不能容納全部的排序記錄&#xff0c;在排序過程中需要訪問外存。 我們這里說說八大排序就是內部排序。 當n較大&#xff0c;則應采…

密鑰安全性討論之密鑰分層管理結構

密鑰分層管理結構 密鑰的安全管理通常采用層次化的保護方法。密鑰管理分層管理機制將密鑰分為三層&#xff0c;即根密鑰、密鑰加密密鑰和工作密鑰下層密鑰為上層密鑰提供加密保護&#xff0c;采用分層的密鑰結構有助于密鑰的管理滿足本規范的要求 工作密鑰 工作密鑰對本地保存…

windows安裝 Git Large File Storage大文件下載工具ge

下載地址 導航到 git-lfs.github.com 并單擊Download開始下載git-lfs的用法指南 驗證安裝成功 打開Git Bash驗證安裝成功&#xff0c;使用命令 git lfs install &#xff0c;如果出現 >Git LFS initlized&#xff0c;就代表安裝成功參考鏈接 安裝 Git Large File Storag…