連貫接口(fluent interface)的Java實現及應用。

幾年前在單元測試時使用mockito和junit(使用hamcrest提供的比較方法)的時候,就用到過這樣相似的語法:html

mockito:java

when(mock.someMethod("some arg"))
  .thenThrow(new RuntimeException())
  .thenReturn("foo");

junit:正則表達式

assertThat(responseString, either(containsString("color")).or(containsString("colour")));

若是按照天然語言來理解,很是清晰:如第一個例子,當被mock的類的某個方法被調用時,先會拋出一個Runtime的異常 而後會返回一個「foo」字符串。編程

可是,若是按照之前咱們學習的java的用法來思惟則會很困惑:when().thenThrow().thenReturn() 這是神馬意思?想半天你纔會猜想着理解這是when()方法返回了某對象有thenThrow()方法,該方法被調用之後又返回了某對象,某對象有thenReturn()方法能夠被調用。我一直把它做爲一種讓編程變得簡單的語法糖,沒有深究。框架

在前一段學着寫寫安卓,第三次發現這種用法,並且被大範圍使用了:例如一個Dialog,你會看到這樣的用法:oop

new AlertDialog.Builder(this)
.setIcon()
.setTitle()
.setPositiveButton()

這又跟咱們平時的用法不大同樣。之前都是new出一個實例來之後,在它的下方整齊的寫n個set來設置它的屬性。這又沒有前面2個例子裏的先後處理關係。因此又習慣性的把它當作了語法糖。單元測試

直到前一段開始看老馬的《特定領域語言》這本書才知道這種用法是一種DSL,老馬還給他起了一個名字 連貫接口(fluent interface)。學習

關於DSL,它最大的用處就是:將常見模式抽取出來,使之變成更加有可讀性的描述方式。是的!有好的可讀性:你之因此以爲正則表達式(另外一種DSL)比一堆String解析代碼難讀,是你沒有付出學習正則表達式的時間。當你掌握了一種DSL的規則後,你會以爲它更簡便,更具備可讀性,你要付出的是學習成本,而帶來的則是效率的提升,與眼前的清爽。測試

上面的3個例子中,咱們發現閱讀更加簡便,代碼量也減小了不少。那麼它是如何實現的呢?其實很簡單,你能夠把它看作是一種責任鏈模式的簡寫/變種。咱們看一段示例代碼,這段代碼中,建立了一個AppoinetmentCalendarChained 的實例calendar。對calendar進行了鏈式操做:add().from().to().at()ui

public class CalendarDemoChained {

    public static void main(String[] args) {
        new CalendarDemoChained();
    }

    public CalendarDemoChained() {
        Calendar fourPM = Calendar.getInstance();
        fourPM.set(Calendar.HOUR_OF_DAY, 16);
        Calendar fivePM = Calendar.getInstance();
        fivePM.set(Calendar.HOUR_OF_DAY, 17);

        AppointmentCalendarChained calendar =
                new AppointmentCalendarChained();
        calendar.add("dentist").
                from(fourPM).
                to(fivePM).
                at("123 main street");

        calendar.add("birthday party").at(fourPM);
        displayAppointments(calendar);
    } 

    private void displayAppointments(AppointmentCalendarChained calendar) {
        for (Appointment a : calendar.getAppointments())
            System.out.println(a.toString());
    }
}

實現鏈式操做的類

public class Appointment {
    private String _name;
    private String _location;
    private Calendar _startTime;
    private Calendar _endTime;

    public Appointment(String name) {
        this._name = name;
    }

    public Appointment() {
    }

    public Appointment name(String name) {
        _name = name;
        return this;
    }
    public Appointment at(String location) {
        _location = location;
        return this;
    }

    public Appointment at(Calendar startTime) {
        _startTime = startTime;
        return this;
    }

    public Appointment from(Calendar startTime) {
        _startTime = startTime;
        return this;
    }

    public Appointment to(Calendar endTime) {
        _endTime = endTime;
        return this;
    }

    public String toString() {
        return "Appointment:"+ _name +
                ((_location != null && _location.length() > 0) ? 
                    ", location:" + _location : "") +
                ", Start time:" + _startTime.get(Calendar.HOUR_OF_DAY) +
                (_endTime != null? ", End time: " + 
                _endTime.get(Calendar.HOUR_OF_DAY) : "");
    }
}

咱們來看add from to 這些方法。每一個方法調用後都把實例自己返回了進去,java支持匿名調用,這樣就能夠直接調用自身方法了,其實挺簡單的。

當鏈式操做有順序的時候(如某些操做必須有一些前置操做),就要多封裝一些複雜邏輯了,老馬的書裏有不少豐富的思路介紹(第10,11,33,38,50章),但並非很是詳細,須要本身再拓展閱讀。

 

btw,要多說2句的是,連貫接口只是DSL的一種,2個常見的測試相關框架已經在大量使用了。去年用的較多的Robotframework 使用的關鍵字驅動也是一種典型的DSL。多學習抽象的方法對自動化框架的設計應該說有很大好處。

 

參考書籍:《特定領域語言》

參考連接:

1.http://martinfowler.com/bliki/FluentInterface.html

2.http://www.ibm.com/developerworks/cn/java/j-eaed13/

3.http://jmock.org/oopsla2006.pdf

相關文章
相關標籤/搜索