Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> 《Android源碼設計模式解析與實戰》讀書筆記(十六)

《Android源碼設計模式解析與實戰》讀書筆記(十六)

編輯:關於android開發

《Android源碼設計模式解析與實戰》讀書筆記(十六)


第十六章、訪問者模式

訪問者模式是一種行為型模式,它是23種設計模式中最復雜的一個,雖然使用頻率不高,但是並不代表可以忽略,在合適的地方,它會帶來意想不到的靈活性。訪問者模式,顧名思義使用了這個模式後就可以在不修改已有程序結構的前提下,通過添加額外的“訪問者”來完成對已有代碼功能的提升。

1.定義

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

2.使用場景

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

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

3.UML類圖

來自《設計模式》

(1)Visitor:接口或者抽象類,它定義了對每一個元素(Element)訪問的行為,它的參數就是可以訪問的元素,它的方法數理論上來講與元素個數是一樣的,因此,訪問者模式要求元素的類族要穩定,如果經常添加、移除元素類,必然會導致頻繁地修改Visitor接口,如果這樣則不適合使用訪問者模式。

(2)ConcreteVisitor1、ConcreteVisitor2:具體的訪問類,它需要給出對每一個元素類訪問時所產生的具體行為。

(3)Element:元素接口或者抽象類,它定義了一個接受訪問者的方法(Accept),其意義是指每一個元素都要可以被訪問者訪問。

(4)ConcreteElementA、ConcreteElementB:具體的元素類,它提供接受訪問方法的具體實現,而這個具體的實現,通常情況下是使用訪問者提供的訪問該元素類的方法。

(5)ObjectStructure:定義當中所說的對象結構,對象結構是一個抽象表述,它內部管理了元素集合,並且可以迭代這些元素供訪問者訪問。

4.簡單實現

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

員工基類:

/**
 * 員工基類(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;//代碼數量

    public Engineer(String name) {
        super(name);
        codeLines = new Random().nextInt(10 * 10000);
    }

    @Override
    public 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);
    }

    @Override
    public 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 {

    @Override
    public void visit(Engineer engineer) {
        System.out.println("攻城獅:" + engineer.name + ", KPI:" + engineer.kpi);
    }

    @Override
    public void visit(Manager manager) {
        System.out.println("經理:" + manager.name + ", KPI:" + manager.kpi
                + ", 新產品數量 :" + manager.getProducts());
    }

}

CTO訪問類:

public class CTOVisitor implements Visitor {

    @Override
    public void visit(Engineer engineer) {
        System.out.println("攻城獅:" + engineer.name + ", 代碼數量:" + engineer.getCodeLines());
    }

    @Override
    public void visit(Manager manager) {
        System.out.println("經理:" + manager.name +", 產品數量 :" + manager.getProducts());
    }

}

員工報表類:

//員工業務報表類(ObjectStructure)
public class BusinessReport {

    List mStaffs = new LinkedList();

    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看報表 =====");
        //設置訪問者CEO
        report.showReport(new CEOVisitor());
        System.out.println("===== 給CTO看報表 =====");
        //設置訪問者CTO
        report.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和類型轉換,這使得代碼難以升級維護。

5.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  the return type of the visitor's methods
     * @param 

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 accept(ElementVisitor v, P p);//接受訪問者的訪問 }

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

public interface ElementVisitor {
    /**
     * 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接口,每個接口處理一種元素類型,那麼這就是典型的訪問者模式。

6.總結

1.優點

(1)各角色職責分離,符合單一職責原則。

(2)具有優秀的擴展性。

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

(4)靈活性。

2.缺點

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

(2)具體元素變更時導致修改成本大。

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

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved