`

关于接口设计,还有Fluent Interface,这种有趣的接口设计风格

阅读更多

这个故事我早就想说了,可能是在好多个月前,只是一直不知道怎么说才能说合适,现在我重新整理了一下,讲述给大家。

 

这个故事是从下面这样一个对外暴露接口的调用开始的。

 

QueryUserEvent event = new QueryUserEvent();
event.setName(name);
event.setAge(18);
event.setType(QueryUserEvent.TYPE_NORMAL);
event.setSex(QueryUserEvent.SEX_MALE);
……
List<User> userList = userService.query(event);

 

我想做的事情其实很简单,我想查询一个用户列表,可是接口参数的拼装让我感到头疼,这样的代码太过啰嗦,我希望有可读性更好的解决办法。

 

P兄台说,如果我直接传入一个user对象,是不是可以避开了这个未必带来多少好处的event?

 

 

User user = new User();
user.setName(name);
user.setAge(18);
user.setSex(QueryUserEvent.SEX_MALE);
……
List<User> userList = userService.query(user, UserService.QUERY_TYPE_NORMAL);
 

我有时候会考虑你说的办法的,可是,你没有解决实际的问题,我现在的最大问题在于,这一堆的setXXX方法,它破坏了我构造这个查询条件对象的流畅性。

 

他紧接着说,那要不然,我们把setXXX方法的劳动省下来,让构造器来替我们完成这个任务吧:

 

User user = new User(name, 18, QueryUserEvent.SEX_MALE, ……);
List<User> userList = userService.query(user, UserService.QUERY_TYPE_NORMAL);

 

我说,你的办法看起来不错,不过有时候按你的办法做,我的构造方法会变得臃肿无比,比如出现十多个参数;

另外还有一个问题,假如说,我的查询条件是简单的(我只需要根据年龄查询),那么其它的参数都要写成null,类似这样子:

 

 

User user = new User(null, 18, null, null, null, null, ……);
List<User> userList = userService.query(user, UserService.QUERY_TYPE_NORMAL);

 

 

天,让谁去阅读这样的代码,他都不会喜欢的。

而且,有时候情形变得复杂,比如,我不是要查询18岁的所有user,而是要查询大于18岁的所有user,你的办法似乎行不通了呢……

 

你真是一个麻烦的人,他说,这样吧,我定义一个Condition,他给查询条件带来了灵活的组装方式:

 

 

UserQueryCondition condition = new UserQueryCondition();
condition.setAge(Condition.GREATER_THAN, 18);
List<User> userList = userService.query(condition, UserService.QUERY_TYPE_NORMAL);

 

 

不过,他补充道,如果在JavaScript中,我可以采取的办法要好得多。如果要查询18岁的和符合其他条件的用户,可以这样写:

 

 

userService.query({
    name : name,
    age : 18,
    sex : User.SEX_MALE
}, UserService.QUERY_TYPE_NORMAL);
 

如果要大于18岁呢,可以这样写:

 

 

userService.query({
    name : name,
    greaterThan : {
        age : 18
    },
    sex : User.SEX_MALE
}, UserService.QUERY_TYPE_NORMAL);

 

不过,他接着说,在Java里面好像还没有类似简洁的表示方法……

 

万幸的是,有一种接口连续调用的风格,叫做“Fluent Interface”,可以让这个问题写成这样一种有趣的实现:

 

 

List<User> userList = new UserService().setName(name).setAge(18).setSex(User.SEX_MALE).query(UserService.QUERY_TYPE_NORMAL);

 

大于18岁的话,这样写:

 

 

List<User> userList = new UserService().setName(name).greaterThan(new User().setAge(18)).setSex(User.SEX_MALE).query(UserService.QUERY_TYPE_NORMAL);

 

我想,这样的设计如果在数学计算的时候,会显得有用得多,看这样一个算式:

ln(sin((x+y)的平方))

如果用传统的方式来实现的话,应该类似这样子:

 

 

Math.log(Math.sin(Math.sqrt(x + y)))

 

显然,它的可读性不如Fluent Interface来得好:

 

 

new MyNumber(x+y).sqrt().sin().log()

 

这样的例子还有很多,比如在JQuery中的使用,在EasyMock中的使用等等。看下面的例子,这样构建一个DOM树,是不是比单纯的字符串拼接,要好理解一些呢?

 

 

$("#div1")
    .div({id:"subDIV"})
        .h1("A Title")
            .a({href:"xxx"})
            .a()
        .h1()
    .div();
 

《CommandQuerySeperation》这篇文章把一个对象的方法大致分成下面两种:

 

Queries: Return a result and do not change the observable state of the system (are free of side effects).

Commands: Change the state of a system but do not return a value.

 

对于Fluent Interface而言,它的接口调用既改变了对象的状态,又返回了对象(this或其他),并不属于上面的两种类型。

 

文章系本人原创,转载请注明出处和作者

 

2
3
分享到:
评论
9 楼 RayChase 2012-02-15  
NicholasBugs 写道
把setName, setAge 重命名为 name, age这样代码就变成了 user.name("Tom").age(18).query(); 更接近自然语言。

8 楼 NicholasBugs 2012-02-14  
把setName, setAge 重命名为 name, age这样代码就变成了 user.name("Tom").age(18).query(); 更接近自然语言。
7 楼 everyday1985 2012-02-07  
貌似这种方式早就有了吧,builder经常这么用,Effective Java里就有些,还有jdom和dom4j里面的format
6 楼 RayChase 2012-02-07  
该用户名已经存在 写道
JDK中Map的put()方法和List的add()方法等返回的是被放进去的那个Object,一直不知道返回这个东西有什么好处?倒不如直接返回this?火哥指教。

对于List来说,你是不是记错了,add返回的是布尔型。
对于Map来说,你可能也有一个误解,返回Value对象可不是做一件无所谓的事情,这个Value可不是你放入的这个Value啊:
See the Documentation of Class Map:
return the previous value associated with key, or null if there was no mapping for key.(A null return can also indicate that the map previously associated null with key, if the implementation supports null values.)
5 楼 RayChase 2012-02-07  
mfkvfn 写道
连缀式语法,不是早就有了么?
Hibernate中的Query就是这种。
jQuery将连缀式语法发扬光大了。

大家似乎只注意到了最后提到的Fluent Interface。
是的,其实很早就有了,最早还是Martin Fowler提出来的概念,有兴趣的话可以去找一下他的blog,我记得里面有。
4 楼 wait10000y 2012-02-07  
又要自定义规则了,去推广吧,等你还没有被认同,又被代替了!
3 楼 该用户名已经存在 2012-02-07  
JDK中Map的put()方法和List的add()方法等返回的是被放进去的那个Object,一直不知道返回这个东西有什么好处?倒不如直接返回this?火哥指教。
2 楼 该用户名已经存在 2012-02-07  
传说中的链式编程么?以前写Filter的时候用到过,我一直认为JDK中Map的put()方法和List的add()方法等应该使用这样的链式编程。
1 楼 mfkvfn 2012-02-07  
连缀式语法,不是早就有了么?
Hibernate中的Query就是这种。
jQuery将连缀式语法发扬光大了。

相关推荐

    FluentInterface:测试自动化和詹金斯项目

    这种接口设计风格使得代码看起来更像是自然语言,增强了代码的可读性和可维护性。在Java编程语言中,Fluent Interface常通过方法链来实现,即一个方法的返回值是当前对象本身,这样可以连续调用多个方法而无需创建新...

    C#中设计、使用Fluent API

    Fluent API,又称链式API,是一种在编程中提供流畅、易读的代码风格的技术。在C#中,它通常通过方法返回自身实例(`return this;`)来实现,允许开发者连续调用多个方法而不会丢失上下文。这种API设计模式在很多流行...

    C#中流畅接口设计的准则-第1部分

    在C#编程中,流畅接口(Fluent Interface)是一种设计模式,它使代码更易于阅读和理解,通过链式方法调用实现。这种接口风格通常用于构建领域特定语言(DSL),使得代码更接近自然语言,提高了代码的可读性和可维护...

    C#中流畅接口设计的准则-第2部分

    这种设计风格尤其适用于领域特定语言(DSL)的构建。在《C#中流畅接口设计的准则-第2部分》中,我们将深入探讨如何有效地应用这一模式,以及它与Windows应用程序开发、Entity Framework和WTL(Windows Template ...

    JavaScript DSL 流畅接口(使用链式调用)实例

    流畅接口(Fluent Interface)是一种编程风格,它鼓励使用方法链(Method Chaining)来编写更易读、更具描述性的代码。方法链允许对象的每个方法返回对象本身,从而使得一系列操作可以通过连续调用来执行。尽管人们...

    Platform SDK( OpenGL Win32 WinUI)

    WinUI提供了丰富的控件集、现代设计风格和 Fluent Design System 支持,使得开发者能够创建出符合最新Windows设计标准的应用。它与传统的Win32 API不同,WinUI更专注于为用户提供一致且美观的界面体验,同时也支持...

    ACP Ribbon 2007 vb源码

    4. **XML 格式和MSO接口**:微软提供了Microsoft Office Fluent User Interface Control Identifier (MSO) XML格式,用于描述Ribbon的布局和行为。开发者可能利用这个XML文件来定义选项卡、组和按钮,然后通过VB代码...

    Applying.Domain.Driven.Design.and.Patterns.With.Examples.in.C.Sharp

    - **技术**:例如使用Fluent Interface来创建更自然流畅的API接口。 9. **面向基础设施的准备** - **技术**:例如使用POCO(Plain Old CLR Object)作为一种编码风格。 - **挑战**:处理保存场景时面临的挑战。 ...

    Office2010-Style.zip_界面编程_Visual_Basic_

    在VB中,我们可以通过自定义控件和布局来模仿这种设计风格。 3. ** Aero效果**:Office 2010利用Windows Aero特效,如玻璃效果和半透明,提供了更美观的外观。VB中的Form控件可以设置TransparencyKey和Opacity属性...

    wpfluent

    2. **Fluent Interface**:Fluent 接口是一种设计模式,使得对象之间的链式调用变得可能,从而提高代码可读性和可维护性。在 wpfluent 中,这可能表现为通过方法链来创建、更新或查询 WordPress 数据。 3. **ORM...

    peliculasApp:可视化的离子5应用程序

    此版本引入了更现代化的设计风格,包括Material Design和Fluent Design的支持,以及对Angular、React和Vue等框架的改进集成。在 `peliculasApp` 中,我们能看到如何使用 `Ionic` 的组件库来创建电影相关的界面,如...

    验证:为PHP创建的最出色的验证引擎

    这种编程风格使得代码更易读、更易写,通过链式调用来构建验证规则,使得代码结构清晰且可维护性高。例如,你可以这样创建一个验证规则: ```php $validator = new Respect\Validation\Validator(); $validator-&gt;...

Global site tag (gtag.js) - Google Analytics