Android編程設(shè)計模式之訪問者模式詳解
本文實例講述了Android編程設(shè)計模式之訪問者模式。分享給大家供大家參考,具體如下:
一、介紹
訪問者模式是一種將數(shù)據(jù)操作與數(shù)據(jù)結(jié)構(gòu)分離的設(shè)計模式,它是《設(shè)計模式》中23種設(shè)計模式中最復(fù)雜的一個,但它的使用頻率并不高,正如《設(shè)計模式》的作者GOF對訪問者模式的描述:大多數(shù)情況下,你不需要使用訪問者模式,但是當(dāng)你一旦需要使用它時,那你就是真的需要它了。
訪問者模式的基本想法是,軟件系統(tǒng)中擁有一個由許多對象構(gòu)成的、比較穩(wěn)定的對象結(jié)構(gòu),這些對象的類都擁有一個accept方法用來接受訪問者對象的訪問。訪問者是一個接口,它擁有一個visit方法,這個方法對訪問到的對象結(jié)構(gòu)中不同類型的元素作出不同的處理。在對象結(jié)構(gòu)的一次訪問過程中,我們遍歷整個對象結(jié)構(gòu),對每一個元素都實施accept方法,在每一個元素的accept方法中會調(diào)用訪問者的visit方法,從而使訪問者得以處理對象結(jié)構(gòu)的每一個元素,我們可以針對對象結(jié)構(gòu)設(shè)計不同的訪問者類來完成不同的操作,達(dá)到區(qū)別對待的效果。
二、定義
封裝一些作用于某種數(shù)據(jù)結(jié)構(gòu)中的各元素的操作,它可以在不改變這個數(shù)據(jù)結(jié)構(gòu)的前提下定義作用于這些元素的新的操作。
三、使用場景
對象結(jié)構(gòu)比較穩(wěn)定,但經(jīng)常需要在此對象結(jié)構(gòu)上定義新的操作。
需要對一個對象結(jié)構(gòu)中的對象進(jìn)行很多不同的并且不相關(guān)的操作,而需要避免這些操作”污染“這些對象的類,也不希望在增加新操作時修改這些類。
四、訪問者模式的UML類圖
UML類圖:
角色介紹:
Visitor:接口或抽象類,定義了對每一個元素的訪問行為,參數(shù)就是可訪問的元素,方法個數(shù)理論上是個元素個數(shù)一樣的。因此,訪問者模式要求被訪問的對象結(jié)構(gòu)要穩(wěn)定,如果經(jīng)常增刪元素,必然會導(dǎo)致頻繁修改Visitor接口,就不適合用訪問者模式了。
ConcreteVisitor:具體的訪問者,定義具體的對每一個元素的具體訪問行為。
Element:抽象的元素接口或抽象類,定義了一個接待訪問者的方法,讓每個元素都可以被訪問者訪問。
ElementA,ElementB:具體的元素類,提供接收訪問方法的具體實現(xiàn)。這個具體實現(xiàn)通常是調(diào)用訪問者提供的訪問該元素的方法。
ObjectStructure:定義對象結(jié)構(gòu),里面維護(hù)了一個元素的集合,并且迭代這些元素供訪問者訪問。
五、簡單示例
情景:年終了,公司會給員工進(jìn)行業(yè)績考核。但是,不同領(lǐng)域的管理人員對于員工的評定標(biāo)準(zhǔn)不一樣?,F(xiàn)在員工有工程師和經(jīng)理,評定者有CEO和CTO,我們假定CTO只關(guān)注工程師的代碼量、經(jīng)理的新產(chǎn)品數(shù)量,而CEO關(guān)注的是工程師的KPI和經(jīng)理的KPI以及新產(chǎn)品數(shù)量。
員工基類:
/** * 員工基類(Element) */ public abstract class Staff { //員工姓名 public String name; //員工KPI public 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;//代碼數(shù)量 public Engineer(String name) { super(name); codeLines = new Random().nextInt(10 * 10000); } @Override public void accept(Visitor visitor) { visitor.visit(this); } //工程師這一年寫的代碼數(shù)量 public int getCodeLines(){ return codeLines; } }
經(jīng)理:
/** * 經(jīng)理 */ public class Manager extends Staff{ private int products;//產(chǎn)品數(shù)量 public Manager(String name) { super(name); products = new Random().nextInt(10); } @Override public void accept(Visitor visitor) { visitor.visit(this); } //一年內(nèi)做的產(chǎn)品數(shù)量 public int getProducts(){ return products; } }
Visitor類:
public interface Visitor { /** * 訪問工程師類型 */ public void visit(Engineer engineer); /** * 訪問經(jīng)理類型 */ public void visit(Manager manager); }
CEO訪問者:
public class CEOVisitor implements Visitor { @Override public void visit(Engineer engineer) { System.out.println("工程師:" + engineer.name + ", KPI:" + engineer.kpi); } @Override public void visit(Manager manager) { System.out.println("經(jīng)理:" + manager.name + ", KPI:" + manager.kpi + ", 新產(chǎn)品數(shù)量 :" + manager.getProducts()); } }
CTO訪問者:
public class CTOVisitor implements Visitor { @Override public void visit(Engineer engineer) { System.out.println("工程師:" + engineer.name + ", 代碼數(shù)量:" + engineer.getCodeLines()); } @Override public void visit(Manager manager) { System.out.println("經(jīng)理:" + manager.name +", 產(chǎn)品數(shù)量 :" + manager.getProducts()); } }
員工報表:
//員工業(yè)務(wù)報表類(ObjectStructure) public class BusinessReport { List<Staff> mStaffs = new LinkedList<Staff>(); public BusinessReport() { mStaffs.add(new Manager("王經(jīng)理")); mStaffs.add(new Engineer("工程師-A")); mStaffs.add(new Engineer("工程師-B")); mStaffs.add(new Manager("李經(jīng)理")); 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) { //構(gòu)建報表 BusinessReport report = new BusinessReport(); System.out.println("===== 給CEO看報表 ====="); //設(shè)置訪問者CEO report.showReport(new CEOVisitor()); System.out.println("===== 給CTO看報表 ====="); //設(shè)置訪問者CTO report.showReport(new CTOVisitor()); } }
結(jié)果:
===== 給CEO看報表 ===== 經(jīng)理:王經(jīng)理, KPI:2, 新產(chǎn)品數(shù)量 :5 工程師:工程師-A, KPI:5 工程師:工程師-B, KPI:7 經(jīng)理:李經(jīng)理, KPI:9, 新產(chǎn)品數(shù)量 :8 工程師:工程師-C, KPI:1 ===== 給CTO看報表 ===== 經(jīng)理:王經(jīng)理, 產(chǎn)品數(shù)量 :5 工程師:工程師-A, 代碼數(shù)量:26238 工程師:工程師-B, 代碼數(shù)量:8282 經(jīng)理:李經(jīng)理, 產(chǎn)品數(shù)量 :8 工程師:工程師-C, 代碼數(shù)量:47927
從上面代碼中可以看出,如果要增加一個訪問者,你新創(chuàng)建一個實現(xiàn)了Visitor接口的類,然后實現(xiàn)兩個visit方法來對不同的元素進(jìn)行不同的操作,從而達(dá)到數(shù)據(jù)對象與數(shù)據(jù)操作相分離的效果。如果不使用訪問者模式,而又想對不同元素進(jìn)行不同的操作,那么必定會使用if-else和類型轉(zhuǎn)換,這使得代碼難以升級維護(hù)。
六、Android中的訪問者模式
安卓中的著名開源庫ButterKnife、Dagger、Retrofit都是基于APT(Annotation Processing Tools)實現(xiàn)。而編譯注解核心依賴APT。當(dāng)我們通過APT處理注解時,最終會將獲取到的元素轉(zhuǎn)換為相應(yīng)的Element元素,以便獲取到它們對應(yīng)信息。那么元素基類的源碼如下:(路徑: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接口,每個接口處理一種元素類型,那么這就是典型的訪問者模式。
七、總結(jié)
正如本節(jié)開頭引用GOF的話所說:大多數(shù)情況下,你不需要使用訪問者模式,但是,當(dāng)你一旦需要使用它時,那你就是真的需要它了。在現(xiàn)實情況下,我們要根據(jù)具體的情況來評估是否適合使用訪問者模式,例如,我們的對象結(jié)構(gòu)是否足夠穩(wěn)定,使用訪問者模式是否能夠優(yōu)化我們的代碼,而不是使我們的代碼變得更復(fù)雜。在使用一個模式之前,我們應(yīng)該明確它的使用場景、它能解決什么問題等,以此來避免濫用設(shè)計模式的現(xiàn)象。
優(yōu)點:
各角色職責(zé)分離,符合單一職責(zé)原則。
具有優(yōu)秀的擴展性。
使得數(shù)據(jù)結(jié)構(gòu)和作用于結(jié)構(gòu)上的操作解耦,使得操作集合可以獨立變化。
靈活性。
缺點:
具體元素對訪問者公布細(xì)節(jié),違反了迪米特原則。
具體元素變更時導(dǎo)致修改成本大。
違反了依賴倒置原則,為了達(dá)到“區(qū)別對待”而依賴了具體類,沒有依賴抽象。
更多關(guān)于Android相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《Android開發(fā)入門與進(jìn)階教程》、《Android調(diào)試技巧與常見問題解決方法匯總》、《Android基本組件用法總結(jié)》、《Android視圖View技巧總結(jié)》、《Android布局layout技巧總結(jié)》及《Android控件用法總結(jié)》
希望本文所述對大家Android程序設(shè)計有所幫助。
相關(guān)文章
Android中使用Gradle來構(gòu)建App項目的入門指南
Gradle是Java世界中一個高人氣自動化構(gòu)建工具,在安卓開發(fā)領(lǐng)域同樣備受追捧,這里為大家?guī)鞟ndroid中使用Gradle來構(gòu)建App項目的入門指南,來看看Gradle的作用與基本結(jié)構(gòu).2016-06-06Android相冊效果(使用C#和Java分別實現(xiàn))
這篇文章主要介紹了Android相冊效果(使用C#和Java分別實現(xiàn)),原來C#也可以開發(fā)APP,小編第一次見了~感覺不錯,因為小編暫時不喜歡Java,所以,需要的朋友可以參考下2015-06-06NestScrollView嵌套RecyclerView實現(xiàn)淘寶首頁滑動效果
這篇文章主要介紹了NestScrollView嵌套RecyclerView實現(xiàn)淘寶首頁滑動效果,主要實現(xiàn)淘寶首頁嵌套滑動,中間tab吸頂效果,以及介紹NestScrollView嵌套RecyclerView處理滑動沖突的方法,需要的朋友可以參考下2021-12-12第1個Android應(yīng)用程序 Android制作簡單單頁導(dǎo)航
這篇文章主要為大家詳細(xì)介紹了第1個Android應(yīng)用程序PhonewordApp:Android制作簡單單頁導(dǎo)航,感興趣的小伙伴們可以參考一下2016-06-06Android中ViewFlipper的使用及設(shè)置動畫效果實例詳解
這篇文章主要介紹了Android中ViewFlipper的使用及設(shè)置動畫效果的方法,以實例形式較為詳細(xì)的分析了ViewFlipper的功能、原理及設(shè)置與使用技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10