Saturday, December 12, 2015

Fluent Interface - DSL



http://www.yegor256.com/2018/03/13/fluent-interfaces.html
In other words, fluent interfaces mean large classes or some ugly workarounds. I mentioned this problem earlier, when I wrote about Streams API and the interface Stream, which is perfectly fluent. There are 43 methods!
That is the biggest problem with fluent interfaces—they force objects to be huge.
Fluent interfaces are perfect for their users, since all methods are in one place and the amount of classes is very small. It is easy to use them, especially with code auto-completion in most IDEs. They also make client code more readable, since "fluent" constructs look similar to plain English (aka DSL).
That is all true! However, the damage they cause to object design is the price, which is too high.

But here is the list of benefits. First, each object is small, very cohesive and they are all loosely coupled—which are obvious merits in OOP. Second, adding new functionality to the library is as easy as creating a new class; no need to touch existing classes. Third, unit testing is simplified, since classes are small. Fourth, all classes can be immutable, which is also an obvious merit in OOP.
Thus, there seems to be a conflict between usefulness and maintainability. Fluent interfaces are good for users, but bad for library developers. Small objects are good for developers, but difficult to understand and use.
It seems to be so, but only if you are used to large classes and procedural programming. To me, a large amount of small classes seems to be an advantage, not a drawback. Libraries that are clear, simple, and readable inside are much easier to use, even when I don't know exactly which classes out there are the most suitable for me. Even without the code-auto-complete I can figure it out myself, because the code is clean.
Also, I very often find myself interested in extending existing functionality either inside my code base or via a pull request to the library. I am much more interested to do that if I know that the changes I introduce are isolated and easy to test.

https://www.javacodegeeks.com/2013/01/fluent-object-creation.html

http://java-design-patterns.com/patterns/fluentinterface/


A fluent interface provides an easy-readable, flowing interface, that often mimics a domain specific language. Using this pattern results in code that can be read nearly as human language.
A fluent interface can be implemented using any of
  • Method Chaining - calling a method returns some object on which further methods can be called.
  • Static Factory Methods and Imports
  • Named parameters - can be simulated in Java using static factory methods.
Use the Fluent Interface pattern when


  • you provide an API that would benefit from a DSL-like usage
  • you have objects that are difficult to configure or use

https://ocramius.github.io/blog/fluent-interfaces-are-evil/
Fluent interfaces make sense in some APIs, like the QueryBuilder , or in general builder objects, especially when it comes to putting together nodes into a hierarchical structure.
$queryBuilder
    ->select('u')
    ->from('User u')
    ->where('u.id = :identifier')
    ->orderBy('u.name', 'ASC')
    ->setParameter('identifier', 100);

https://blog.crisp.se/2013/10/09/perlundholm/another-builder-pattern-for-java
1HttpRequest request = HttpRequest
2        .url(SOME_URL)
3        .headers(HttpRequest.Headers
4                .header(ContentType, SOME_MIME_TYPE)
5                .header(SOME_CUSTOM_HEADER, SOME_CUSTOM_VALUE)
6                .build()
7        )
8        .parameters(HttpRequest.Parameters
9                .parameter(SOME_KEY, SOME_VALUE)
10                .parameter(SOME_KEY_2, SOME_VALUE_2)
11                .build())
12        .build();
https://blog.jooq.org/2012/01/05/the-java-fluent-api-designer-crash-course/
https://zeroturnaround.com/rebellabs/a-fluent-api-or-not-a-fluent-api-that-is-the-question/
We call this method chaining. We now have a problem, because if we try to get the setter method using the previous technique, you will get a java.beans.IntrospectionException: Method not found: setBar.
https://martinfowler.com/bliki/FluentInterface.html
The most common way I see this kind of thing built up is like this:
    private void makeNormal(Customer customer) {
        Order o1 = new Order();
        customer.addOrder(o1);
        OrderLine line1 = new OrderLine(6, Product.find("TAL"));
        o1.addLine(line1);
        OrderLine line2 = new OrderLine(5, Product.find("HPK"));
        o1.addLine(line2);
        OrderLine line3 = new OrderLine(3, Product.find("LGV"));
        o1.addLine(line3);
        line2.setSkippable(true);
        o1.setRush(true);
    }
In essence we create the various objects and wire them up together. If we can't set up everything in the constructor, then we need to make temporary variables to help us complete the wiring - this is particularly the case where you're adding items into collections.
Here's the same assembly done in a fluent style:
   private void makeFluent(Customer customer) {
        customer.newOrder()
                .with(6, "TAL")
                .with(5, "HPK").skippable()
                .with(3, "LGV")
                .priorityRush();
    }

You should choose your return type based on what you need to continue fluent action. JMock makes a big point of moving its types depending on what's likely to be needed next. One of the nice benefits of this style is that method completion (intellisense) helps tell you what to type next - rather like a wizard in the IDE

It's only in the context of the fluent action that it shows its strengths. One way around this may be to use builder objects that are only used in this context.

 fluent interfaces mostly around configurations of value objects. Value objects don't have domain-meaningful identity so you can make them and throw them away easily. So the fluency rides on making new values out of old values
https://dzone.com/articles/java-fluent-api-design
http://javaonfly.blogspot.com/2016/08/java-fluent-api-design.html
In this article we will discuss about how to design fluent API in Java. The term Fluent interface is coined by Martin Fowler and Eric Evans. Fluent API means, build an API in such way so that same meets following criteria.
a.       API user can understand API very easily.
b.       API can perform a series of actions in order to finish a task, in java we can do it by series of method calls (Chaining of methods).
c.       Each Methods name should be Domain specific terminology.
d.       API should suggestive enough to guide API Users, what to do next and What are the possible operations users can take at a particular moment.
Suppose You want to Design an API for a Domain, say (Retail) so There should be some common terminology exists in Retail domain and for a certain context(Task) it will take a series of actions to finish this task. Say for an invoice generation it has to follow certain steps. Now when you design API, you should design it such a way, when API Users call Billing Service for invoice generation, API User can fluently perform each step in order to complete invoice generation and API will assist user to perform those steps upon invoking Billing Service. When a API method invokes by an user, method will perform its task and returns a Domain Object which will assist what to do next, until all steps are executed.  Unlike Standard API it is API user job to call API methods in a sequential way to successfully performs a task. So API Users has to know about the service steps very well.

new Arsalan().name("ARSALAN").show().order(0).order(1).eat().pay();

http://coolshell.cn/articles/5709.html
程序设计语言的抽象机制包含了两个最基本的方面:一是语言关注的基本元素/语义;另一个是从基本元素/语义到复合元素/语义的构造规则。在C、C++、Java、C#、Python等通用语言中,语言的基本元素/语义往往离问题域较远,通过API库的形式进行层层抽象是降低问题难度最常用的方法。比如,在C语言中最常见的方式是提供函数库来封装复杂逻辑,方便外部调用。
不过普通的API设计方法存在一种天然的陷阱,那就是不管怎样封装,大过程虽然比小过程抽象层次更高,但本质上还是过程,受到过程语义的制约。也就是说,通过基本元素/语义构造更高级抽象元素/语义的时候,语言的构造规则很大程度上限制了抽象的维度,我们很难跳出这个维度去,甚至可能根本意识不到这个限制。而SQL、HTML、CSS、make等DSL(领域特定语言)的抽象维度是为特定领域量身定做的,从这些抽象角度看问题往往最为简单,所以DSL在解决其特定领域的问题时比通用程序设计语言更加方便。通常,SQL等非通用语言被称为外部DSL(External DSL);在通用语言中,我们其实也可以在一定程度上突破语言构造规则的抽象维度限制,定义内部DSL(Internal DSL)。
本文将介绍一种被称为流畅接口(Fluent Interface)的内部DSL设计方法。Wikipedia上Fluent Interface的定义是:
A fluent interface (as first coined by Eric Evans and Martin Fowler) is an implementation of an object oriented API that aims to provide for more readable code. A fluent interface is normally implemented by using method chaining to relay the instruction context of a subsequent call (but a fluent interface entails more than just method chaining).
下面将分4个部分来逐步说明流畅接口在构造内部DSL中的典型应用。

1. 基本语义抽象

如果要输出0..4这5个数,我们一般会首先想到类似这样的代码:
1
2
3
4
//Java
for (int i = 0; i < 5; ++i) {
    system.out.println(i);
}
而Ruby虽然也支持类似的for循环,但最简单的是下面这样的实现:
1
2
//Ruby
5.times {|i| puts i}
Ruby中一切皆对象,5是Fixnum类的实例,times是Fixnum的一个方法,它接受一个block参数。相比for循环实现,Ruby的times方式更简洁,可读性更强,但熟悉OOP的朋友可能会有疑问,times是否应该作为整型类的方法呢?在OOP中,方法调用通常代表了向对象发送消息,改变或查询对象的状态,times方法显然不是对整型对象状态的查询和修改。如果你是Ruby的设计者,你会把times方法放入Fixnum类吗?如果答案是否定的,那么Ruby的这种设计本质上代表了什么呢?实际上,这里的times虽然只是一个普通的类方法,但它的目的却与普通意义上的类方法不同,它的语义实际上类似于for循环这样的语言基本语义,可以被视为一种自定义的基本语义。times的语义从一定程度上跳出了类方法的框框,向问题域迈进了一步!
另一个例子来自Eric Evans的“用两个时间点构造一个时间段对象”,普通设计:
1
2
3
//Java
TimePoint fiveOClock, sixOClock;
TimeInterval meetingTime = new TimeInterval(fiveOClock, sixOClock);
另一种Evans的设计是这样:
1
2
//Java
TimeInterval meetingTime = fiveOClock.until(sixOClock);
按传统OO设计,until方法本不应出现在TimePoint类中,这里TimePoint类的until方法同样代表了一种自定义的基本语义,使得表达时间域的问题更加自然。
虽然上面的两个简单例子和普通设计相比看不出太大的优势,但它却为我们理解流畅接口打下了基础。重要的是应该体会到它们从一定程度上跳出了语言基本抽象机制的束缚,我们不应该再用类职责划分、迪米特法则(Law of Demeter)等OO设计原则来看待它们。

2. 管道抽象

在Shell中,我们可以通过管道将一系列的小命令组合在一起实现复杂的功能。管道中流动的是单一类型的文本流,计算过程就是从输入流到输出流的变换过程,每个命令是对文本流的一次变换作用,通过管道将作用叠加起来。在Shell中,很多时候我们只需要一句话就能完成log统计这样的中小规模问题。和其他抽象机制相比,管道的优美在于无嵌套。比如下面这段C程序,由于嵌套层次较深,不容易一下子理解清楚:
1
2
//C
min(max(min(max(a,b),c),d),e)
而用管道来表达同样的功能则清晰得多:
1
2
#!/bin/bash
max a b | min c | max d | min e
我们很容易理解这段程序表达的意思是:先求a, b的最大值;再把结果和c取最小值;再把结果和d求最大值;再把结果和e求最小值。
jQuery的链式调用设计也具有管道的风格,方法链上流动的是同一类型的jQuery对象,每一步方法调用是对对象的一次作用,整个方法链将各个方法的作用叠加起来。
1
2
//Javascript
$('li').filter(':event').css('background-color', 'red');

3. 层次结构抽象

除了管道这种“线性”结构外,流畅接口还可用于构造层次结构抽象。比如,用Javascript动态创建创建下面的HTML片段:
1
2
3
4
5
6
7
<div id="’product_123’" class="’product’">
<img src="’preview_123.jpg’" alt="" />
<ul>
    <li>Name: iPad2 32G</li>
    <li>Price: 3600</li>
</ul>
</div>
若采用Javascript的DOM API:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Javascript
var div = document.createElement('div');
div.setAttribute(‘id’, ‘product_123’);
div.setAttribute(‘class’, ‘product’);
var img = document.createElement('img');
img.setAttribute(‘src’, ‘preview_123.jpg’);
div.appendChild(img);
var ul = document.createElement('ul');
var li1 = document.createElement('li');
var txt1 = document.createTextNode("Name: iPad2 32G");
li1.appendChild(txt1);
div.appendChild(ul);
而下面流畅接口API则要有表现力得多:
1
2
3
4
5
6
7
8
9
//Javascript
var obj =
$.div({id:’product_123’, class:’product’})
    .img({src:’preview_123.jpg’})
    .ul()
        .li().text(‘Name: iPad2 32G’)._li()
        .li().text(‘Price: 3600’)._li()
    ._ul()
 ._div();
和Javascript的标准DOM API相比,上面的API设计不再局限于孤立地看待某一个方法,而是考虑了它们在解决问题时的组合使用,所以代码的表现形式特别贴近问题的本质。这样的代码是自解释的(self-explanatory)在可读性方面要明显胜于DOM API,这相当于定义了一种类似于HTML的内部DSL,它拥有自己的语义和语法。需要特别注意的是,上面的层次结构抽象和管道抽象有着本质的不同,管道抽象的方法链上通常是同一对象的连续传递,而层次抽象中方法链上的对象却在随着层次的变化而变化。此为,我们可以把业务规则也表达在流畅接口中,比如上面的例子中,body()不能包含在div()返回的对象中,div().body()将抛出”body方法不存在”异常。

4. 异步抽象

流畅接口不仅可以构造复杂的层次抽象,还可以用于构造异步抽象。在基于回调机制的异步模式中,多个异步调用的同步和嵌套问题是使用异步的难点所在。有时一个稍复杂的调用和同步关系会导致代码充满了复杂的同步检查和层层回调,难以理解和维护。这个问题从本质上讲和上面HTML的例子一样,是由于多数通用语言并未把异步作为基本元素/语义,许多异步实现模式是向语言的妥协。针对这个问题,我用Javascript编写了一个基于流畅接口的异步DSL,示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//Javascript
$.begin()
    .async(newTask('task1'), 'task1')
    .async(newTask('task2'), 'task2')
    .async(newTask('task3'), 'task3')
.when()
    .each_done(function(name, result) {
        console.log(name + ': ' + result);})
    .all_done(function(){ console.log('good, all completed'); })
    .timeout(function(){
        console.log('timeout!!');
        $.begin()
            .async(newTask('task4'), 'task4')
        .when()
            .each_done(function(name, result) {
                console.log(name + ': ' + result); })
        .end();}
        , 3000)
.end();
上面的代码只是一句Javascript调用,但从另一个角度看它却像一段描述异步调用的DSL程序。它通过流畅接口定义了begin when end的语法结构,begin后面跟的是启动异步调用的代码;when后面是异步结果处理,可以选择each_done, all_done, timeout中的一种或多种。而begin when end结构本身是可以嵌套的,比如上面的代码在timeout处理分支中就包含了另一个begin when end结构。通过这个DSL,我们可以比基于回调的方式更好地表达异步调用的同步和嵌套关系。
上面介绍了用流畅接口构造的4种典型抽象,出此之外还有很多其他的抽象和应用场合,比如:不少单元测试框架就通过流畅接口定义了单元测试的DSL。虽然上面的例子以Javascript等动态语言居多,但其实流畅接口所依赖的语法基础并不苛刻,即使在Java这样的静态语言中,同样可以轻松地使用。流畅接口不同于传统的API设计,理解和使用流畅接口关键是要突破语言抽象机制带来的定势思维,根据问题域选取适当的抽象维度,利用语言的基本语法构造领域特定的语义和语法。
参考

Labels

Review (572) System Design (334) System Design - Review (198) Java (189) Coding (75) Interview-System Design (65) Interview (63) Book Notes (59) Coding - Review (59) to-do (45) Linux (43) Knowledge (39) Interview-Java (35) Knowledge - Review (32) Database (31) Design Patterns (31) Big Data (29) Product Architecture (28) MultiThread (27) Soft Skills (27) Concurrency (26) Cracking Code Interview (26) Miscs (25) Distributed (24) OOD Design (24) Google (23) Career (22) Interview - Review (21) Java - Code (21) Operating System (21) Interview Q&A (20) System Design - Practice (20) Tips (19) Algorithm (17) Company - Facebook (17) Security (17) How to Ace Interview (16) Brain Teaser (14) Linux - Shell (14) Redis (14) Testing (14) Tools (14) Code Quality (13) Search (13) Spark (13) Spring (13) Company - LinkedIn (12) How to (12) Interview-Database (12) Interview-Operating System (12) Solr (12) Architecture Principles (11) Resource (10) Amazon (9) Cache (9) Git (9) Interview - MultiThread (9) Scalability (9) Trouble Shooting (9) Web Dev (9) Architecture Model (8) Better Programmer (8) Cassandra (8) Company - Uber (8) Java67 (8) Math (8) OO Design principles (8) SOLID (8) Design (7) Interview Corner (7) JVM (7) Java Basics (7) Kafka (7) Mac (7) Machine Learning (7) NoSQL (7) C++ (6) Chrome (6) File System (6) Highscalability (6) How to Better (6) Network (6) Restful (6) CareerCup (5) Code Review (5) Hash (5) How to Interview (5) JDK Source Code (5) JavaScript (5) Leetcode (5) Must Known (5) Python (5)

Popular Posts