zoukankan      html  css  js  c++  java
  • 连贯接口(fluent interface)的Java实现及应用。

    几年前在单元测试时使用mockito和junit(使用hamcrest提供的比较方法)的时候,就用到过这样类似的语法:

    mockito:

    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,你会看到这样的用法:

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

    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

  • 相关阅读:
    python3删除mysql上月分区数据(脚本)
    ansible之基本原理及命令
    centOS 7 简单设置(虚拟机)
    TCP_Wrappers 简介
    sudo
    引用数据应该选择 ID, CODE 还是 NAME
    吃得洒脱是一种什么体验
    通用数据同步机制
    我的学PyTorch之路(1)
    38岁才学会了游泳的心得
  • 原文地址:https://www.cnblogs.com/skytraveler/p/3515519.html
Copyright © 2011-2022 走看看