Java链式方法 连贯接口(fluent interface)

有两种情况可运用链式方法:

 

第一种  除最后一个方法外,每个方法都返回一个对象

 

object2 = object1.method1();

object3 = object2.method2();

object3.method3();

 

以上三行代码等价于如下链式方法

object1.method1().method2().method3();

 

第二种 调用同一个对象的多个方法

 

user.setName(String name);

user.setPassword(String password);

user.setRole(String role);

 

以上三行代码等价于如下链式方法

user

      .setName(String name)

      .setPassword(String password)

      .setRole(String role);

 

示例:

使用chaining:

package com.dxz.base;

public class Persion {
    private int id;
    private String name;
    private String phoneNumber;
    private String address;

    public Persion() {

    }

    public Persion setId(int id) { 
        this.id = id;
        return this;
    }
    public Persion setName(String name) {
        this.name = name;
        return this;
    }
    public Persion setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
        return this;
    }
    public Persion setAddress(String address) {
        this.address = address;
        return this;
    }
    public Persion printId() {
        System.out.println(this.id);
        return this;
    }
    public Persion printName() {
        System.out.println(this.name);
        return this;
    }
    public Persion printPhoneNumber() {
        System.out.println(this.phoneNumber);
        return this;
    }
    public Persion printAddress() {
        System.out.println(this.address);
        return this;
    }
}
package com.dxz.base;

public class Test {
    public static void main(String[] args) {
        /*Persion persion1 = new Persion();
        persion1.setId(3);
        persion1.setName("John");
        persion1.setPhoneNumber("1111111");
        persion1.setAddress("US");
        persion1.printId();
        persion1.printName();
        persion1.printPhoneNumber();
        persion1.printAddress();*/
        Persion persion1 = new Persion();
        persion1.setId(3).setName("John")
                .setPhoneNumber("1111111").setAddress("US");
        persion1.printId()
                .printName()
                .printPhoneNumber()
                .printAddress();
    }
}

结果:

3
John
1111111
US

 

直到前一段开始看老马的《特定领域语言》这本书才知道这种用法是一种DSL,老马还给他起了一个名字 连贯接口(fluent interface)。

关于DSL,它最大的用处就是:将常见模式抽取出来,使之变成更加有可读性的描述方式。是的!有好的可读性:你之所以觉得正则表达式(另一种DSL)比一堆String解析代码难读,是你没有付出学习正则表达式的时间。当你掌握了一种DSL的规则后,你会觉得它更简便,更具有可读性,你要付出的是学习成本,而带来的则是效率的提高,与眼前的清爽。

上面的3个例子中,我们发现阅读更加简便,代码量也减少了很多。那么它是如何实现的呢?其实很简单,你可以把它看做是一种责任链模式的简写/变种。我们看一段示例代码,这段代码中,创建了一个AppoinetmentCalendarChained 的实例calendar。对calendar进行了链式操作:add().from().to().at()

复制代码
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

posted on 2016-04-12 23:02  duanxz  阅读(1508)  评论(0编辑  收藏  举报