1. 程式人生 > >Android設計模式之訪問者模式

Android設計模式之訪問者模式

訪問者模式是一種將資料庫操作與資料結構分離的設計模式。

訪問者模式的基本思想


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


          訪問者模式的定義

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


          訪問者模式的使用場景


1.物件結構比較穩當,但經常需要在此物件結構上定義新的操作。
2.需要對一個物件結構中的物件進行很多不同的並且不相關的操作,兒需要避免這些操作“汙染”這些物件的類,也不希望在增加新操作時修改這些類。

 

角色介紹

Visitor:介面或者抽象類,它定義了對每個元素(Element)訪問的行為,它的引數就是可以訪問的元素,它的方法個數理論上與元素個數是一樣的。因此,訪問者模式要求元素的類族要穩定,如果經常新增、移除元素類,必然會頻繁修改Visitor介面,如果出現這種情況,則說明不適合使用訪問者模式。

ConcreteVisitor:具體的訪問者,它需要給出對每一個元素類訪問時所產生的具體行為。

Element:元素介面或者抽象類,它提供接受訪問者(accept)方法,其意義是指每一個元素都要可以被訪問者訪問。

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

ObjectStructure:定義當中所提到的物件結構,物件結構是一個抽象描述,它內部管理了元素的集合,並且可以迭代這些元素供訪問者訪問。

 

訪問者模式簡單Demo

package visitorpattern.gome.com.visitorpattern;
import java.util.Random;
/**
 * Created by ying.zhang on 2018/8/13.
 * 員工基類
 * Staff類定義了員工的基本資訊以及一個accept方法,accept方法表示接受訪問者的訪問,由子類具體實現。
 */
public abstract class Staff {

    public String name;
    //員工kpi
    public int kpi;
    public Staff(String aName) {
        this.name = aName;
        kpi = new Random().nextInt(10);
    }
    //接受Visitor的訪問
    public abstract void accept(Visitor visitor);
}
package visitorpattern.gome.com.visitorpattern;
import java.util.Random;
/**
 * Created by ying.zhang on 2018/8/13.
 * 工程師類中添加了獲取程式碼行數的函式,而經理型別中則添加了獲取新產品數量的函式,
 * 它們的職責是不一樣的,也正是它們的差異性,才使得訪問者模式能夠發揮它是作用。
 * Staff、Engineer、Manager3個型別就是物件結構,這些型別相對穩定,不會發生變化。
 */

public class Engineer extends Staff {

    public Engineer(String aName) {
        super(aName);
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    //工程師這一年寫的程式碼數量
    public int getCodeLines() {
        return new Random().nextInt(10*10000);
    }
}

 

package visitorpattern.gome.com.visitorpattern;
import java.util.Random;
/**
 * Created by ying.zhang on 2018/8/13.
 */
public class Manager extends Staff {

    private int products;// 產品數量

    public Manager(String aName) {
        super(aName);
        products = new Random().nextInt(10);
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    //一年內做的產品數量
    public int getProducts() {
        return products;
    }
}

 

package visitorpattern.gome.com.visitorpattern;
/**
 * Created by ying.zhang on 2018/8/13.
 */
public interface Visitor {

    //訪問工程師型別
    public void visit(Engineer engineer);
    //訪問經理型別
    public void visit(Manager manager);

}

 

package visitorpattern.gome.com.visitorpattern;

import android.util.Log;

/**
 * Created by ying.zhang on 2018/8/13.
 * 在CEO的訪問者中,CEO只關注Engineer員工的KPI,而對於Manager型別的員工除了KPI之外,還有該Manager本年度新開發產品的數量。
 * 兩類員工關注點不同,通過兩個visitor方法分別進行處理。
 */

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 + ",新產品數量:" + manager.getProducts());
    }
}

 

package visitorpattern.gome.com.visitorpattern;

import android.util.Log;

/**
 * Created by ying.zhang on 2018/8/13.
 */

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());
    }
}

 

package visitorpattern.gome.com.visitorpattern;

import java.util.LinkedList;
import java.util.List;

/**
 * Created by ying.zhang on 2018/8/13.
 * 員工業務報表1類
 *
 * 然後將這些員工新增到一個業務報表類中,公司高層通過該報表類的showReport函式檢視所有員工的業績
 */

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);
        }
    }
}

 

package visitorpattern.gome.com.visitorpattern;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

    public static String TAG = "VisitorLOG";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //構建報表
        BusinessReport report = new BusinessReport();
        Log.v(TAG, "--------CEO visit--------------");
        //設定訪問者,這裡是CEO
        report.showReport(new CEOVisitor());
        Log.v(TAG, "--------CTO visit-------------");
        //注入另一個訪問者, CTO
        report.showReport(new CTOVisitor());
    }
}

 

Log分析:

V/VisitorLOG: --------CEO visit--------------

V/VisitorLOG: mananger:manager-1, KPI:0,新產品數量: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,新產品數量: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

 

 

 

上述示例中,Staff扮演了Element角色,而Engineer和Manager都是ConcreteElement;CEOVisitor和CTOVisitor都是具體的Visitor物件;而BusinessReport就是ObjectStructure;Client就是客戶端程式碼。

訪問者模式最大的優點就是增加訪問者非常容易,從程式碼中可以看到,如果要增加一個訪問者,你新建立一個實現了Visitor介面的類,然後實現兩個visit函式來對不同的元素進行不同的操作,從而達到資料物件與資料操作分離的效果。

 

 

 

在現階段的Android開發中,註解越來越流行,按照處理時期,註解又可以分為兩種型別,一種是執行時註解,另一種是編譯時註解。

執行時註解因為效能問題被一些人詬病,編譯時註解的核心原理依賴APT(Annotation Processing Tools)實現,例如ButterKnife、Dagger、Retrofit等開源庫都是基於APT。那麼編譯時註解是如何工作的?它與訪問者模式又有什麼樣的關係?

編譯時Annotation解析的基本原理是,在某些程式碼元素上(如型別、函式、欄位等)添加註解,在編譯時編譯器會檢查AbstractProcessor的子類,並且呼叫該型別的process函式,然後將添加了註解的所有元素都傳遞到process函式中,使得開發人員在編譯期間進行相應的處理,例如根據註解生成新的Java類,這也是ButterKnife等開源庫的基本原理。

在編譯處理的時候,是分開進行的。如果在某個處理中產生了新的Java原始檔,那麼就需要另外一個處理來處理新生成的原始檔,如此往復,直到沒有新檔案被生成為止。在完成處理之後,再對Java程式碼進行編譯。JDK5.0中提供了APT工具用來對註解進行處理。APT是一個命令列工具,與之配套的還有一套用來描述語義結構的Mirror API,Mirror API(com.sun.mirror.*)描述的是程式在編譯時的靜態結構,通過Mirror API可以獲取到被註解的Java型別元素的資訊,從而提供相應的邏輯處理,具體的處理工作交給APT來完成。編寫註解處理器的核心是AnnotationProcessorFactory 和AnnotationProcessor兩個介面,後者表示註解處理器,而前者則為某些註解型別建立註解處理器的工廠。

對於編譯器來說,程式碼中的元素結構是基本不變的,例如,組成程式碼的基本元素有包、類、函式、欄位、型別引數、變數。JDK中為這些元素定義了一個基類,也就是Element類,它有如下幾個子類:

1.PackageElement——包元素,包含了某個包下的資訊,可以獲取到包名等;

2.TypeElement——型別元素,如某個欄位屬於某種型別;

3.ExecutableElement——可執行元素,代表了函式型別的元素;

4.VariableElement——變數元素

5.TypeParameterElement——型別引數元素

 

因為註解可以指定作用在哪些元素上,因此,通過上述抽象來對應這些元素,例如下面這個註解,指定的是隻能作用在函式上面,並且這個註解只能保留在class檔案中。

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.CLASS)

@interface Test{

String value();

}

該註解因為只能作用於函式型別,因此,它對應的元素型別就是ExecutableElement,當我們想通過ADT處理這個註解時就可以獲取目標物件上的Text註解,並且將所有這些元素轉換為ExecutableElement元素,以便獲取到它們對應的資訊。

可以看看元素基類的實現,完整的路徑是javax.lang.model.element.Element
/**
 * Android中找不到Element類
 */

public interface Element {
    //程式碼省略
    //獲取元素型別
    ElementKind getKind();
    
    //獲取修飾符,如public 、 static 、final等
    Set<Modifier> getModifiers();

    //接受訪問者的訪問
    <R, P> R accept(ElementVisitor<R, P> P p);
}


/**
 * Android中找不到ElementVisitor類
 * 元素訪問者
 */
public interface ElementVisitor<R, P> {

    //訪問元素
    R visit(Element e, P p);

    //訪問包元素
    R visitPackage(PackageElement e, P p);

    //訪問一個型別元素
    R visitType(TypeElement e, P p);

    //訪問一個變數型別
    R visitVariable(VariableElement e, P p);

    //訪問一個可執行元素
    R visitExecutable(ExecutableElement e, P p);

    //訪問一個引數元素
    R visitTypeParameter(TypeParameterElement e, P p);

    //處理未知的元素型別,這是為了應對後續java語言的擴充套件而預留的介面
    //例如後續元素型別增加,那麼通過這個介面就可以處理上述沒有宣告的型別
    R visitUnknown(Element e, P p);
}

在ElementVisitor中定義了多個visit介面,每個介面處理一種元素型別,這就是典型的訪問者模式。一個類元素和一個函式元素是完全不一樣的,它們的結構不一樣,因此,編譯器對它們的操作也不同,通過訪問者模式正好解決資料結構與資料操作 分離的問題,避免這些操作“汙染”資料物件類。但是,與經典的訪問者模式不同的是,ElementVisitor預留了一個visitUnKnown函式類應對元素結構的變化,例如如果後續在Java語言中添加了某個新的元素型別叫做NewElement,那麼這個元素就會被visitUnknown函式訪問,此時JDK只需要繼承現有的Visitor類,然後覆寫visitUnknown函式並且對NewElement進行相應的處理即可應對變化。

當Visitor對元素結構進行訪問時就可以針對不同型別進行不同的處理,例如SimpleElementVisitor6就是其中一個訪問者,它基本什麼都不做,直接返回元素的預設值,具體程式碼如下:

public class SimpleEmentVisitor6<R, P> extends AbstractElementVisitor6<R, P> {

    protected final R DEFAULT_VALUE;
    protected SimpleEmentVisitor6(R defaultValue) {
        DEFAULT_VALUE = defaultValue;
    }
    protected R defaultAction(Element e, P p) {
        return DEFAULT_VALUE;
    }
    public R visitType(TypeElement e, P p) {
        return defaultAction(e, p);
    }
    public R visitExecuteable(ExecutableElement e, P p) {
        return defaultAction(e, p);
    }
    //程式碼省略
}

 

參考《Android原始碼設計模式》