New API design thinking: build internal DSL with smooth interfaces

Source: Internet
Author: User

The abstract mechanism of programming languages includes two basic aspects: one is the basic elements/semantics of language attention, and the other is the construction rules from basic elements/semantics to composite elements/semantics. In general languages such as C, C ++, Java, C #, and python, the basic elements/semantics of a language are often far from the problematic domain, layer-by-layer abstraction through the api library is the most common method to reduce the difficulty of the problem. For example, the most common method in C is to provide a function library to encapsulate complex logic and facilitate external calls.

However, a common API design method has a natural trap, that is, no matter how encapsulated, although a large process has a higher abstraction level than a small process, it is essentially a process and restricted by the process semantics. That is to say, when constructing more advanced abstract elements/semantics through basic elements/semantics, the language construction rules limit abstract dimensions to a large extent, and it is difficult for us to jump out of this dimension, you may not even be aware of this restriction. However, the abstract dimensions of DSL (domain-specific language) such as SQL, HTML, CSS, and make are customized for specific fields. From these abstract perspectives, the problem is often the easiest, therefore, DSL is more convenient than general programming languages in solving problems in specific fields. Generally, non-General languages such as SQL are called external DSL (external
DSL); In general languages, we can break through the abstract dimension restrictions of the Language Construction rules to some extent and define the internal DSL (internal DSL ).

This article introduces an internal DSL design method called fluent interface. The definition of fluent interface on Wikipedia is:

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

The following section describes the typical application of a smooth interface in constructing an internal DSL.

1. Basic Semantic Abstraction

To output the numbers 0 .. 4, we generally first think of code like this:

//Javafor (int i = 0; i < 5; ++i) {    system.out.println(i);}

Although Ruby also supports a similar for loop, the simplest is the following implementation:

//Ruby5.times { |i| puts i }

Everything in ruby is an object. 5 is an instance of the fixnum class. Times is a method of fixnum and it accepts a block parameter. Compared with for loop implementation, Ruby's Times method is simpler and more readable. However, if you are familiar with OOP, you may wonder if times should be used as an integer method? In Oop, a method call usually refers to sending a message to an object, changing or querying the object state. The Times method is obviously not querying or modifying the status of an integer object. If you are the ruby designer, will you put the Times method into the fixnum class? If the answer is no, what does Ruby's design actually mean? In fact, although times is just a common class method, its purpose is different from that in the general sense. Its semantics is actually similar to the Basic Semantics of a language like for loop, it can be considered as a basic custom semantics. The meaning of times has jumped out of the box of class methods to a certain extent, and has taken a step towards the problem domain!

In another example, Eric Evans uses two time points to construct a time period object. The general design is as follows:

//JavaTimePoint fiveOClock, sixOClock;TimeInterval meetingTime = new TimeInterval(fiveOClock, sixOClock);

The Design of Evans is as follows:

//JavaTimeInterval meetingTime = fiveOClock.until(sixOClock);

According to the traditional OO design, the until method should not appear in the timepoint class. Here, the until method of the timepoint class also represents a custom Basic Semantics, this makes the problem of expressing time domains more natural.

Although the two simple examples above do not show much advantage over the ordinary design, it lays the foundation for us to understand the smooth interface. The important thing is that they have jumped out of the limitations of the basic language abstraction mechanism to a certain extent. We should not use the law of Demeter) and so on.

2. Pipeline Abstraction

In Shell, we can combine a series of small commands through pipelines to implement complicated functions. A single type of text stream flows flow in the pipeline, and the computing process is the conversion process from the input stream to the output stream. Each Command is a transformation of the text stream, adds functions through pipelines. In Shell, we often need only one sentence to complete small and medium scale issues such as log statistics. Compared with other abstract mechanisms, pipelines are not nested. For example, the following section of the C program is not easy to understand because of its deep hierarchy:

//Cmin(max(min(max(a, b), c), d), e)

Using pipelines to express the same function is much clearer:

//Bashmax a b | min c | max d | min e

We can easily understand the meaning of this program: calculate the maximum value of A and B first, then take the result and C to the minimum value, and then the result and D to the maximum value; then calculate the minimum value of the result and E.

The chain call Design of jquery also has the pipeline style. The method chain flows through the same type of jquery object. Each method call is a function of the object, the entire method chain overlays the functions of each method.

//jQuery$('li').filter(':even').css('background-color', 'red');

3. Layered Abstraction

In addition to the linear structure of pipelines, smooth interfaces can also be used to construct layered abstraction. For example, you can use JavaScript to dynamically create the following HTML snippet:

<div id="product_123" class="product">       <ul>        <li>Name: iPad2 32G</li>        <li>Price: 3600</li>    </ul></div>

If Javascript DOM APIs are used, their readability and maintainability are not good enough:

//Javascriptvar 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);

The following smooth API is more expressive:

//Javascriptvar 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();

Compared with the standard DOM APIs of JavaScript, the above API design does not only look at a method in isolation, but considers the combined use of them in solving the problem, therefore, the code is very close to the nature of the problem. Such code is self-explanatory much better than dom APIs in terms of readability, which is equivalent to defining an internal DSL similar to HTML, it has its own semantics and syntax. Note that the layered structure abstraction and pipeline abstraction are essentially different. The pipeline abstraction method chain is usually the continuous transmission of the same object, the objects in the method chain in hierarchical abstraction change with the change of layers. In addition, we can also express business rules in smooth interfaces. For example, in the above example, body () cannot be included in the object returned by Div (), Div (). body () will throw the "body method does not exist" exception.

4. asynchronous Abstraction

A smooth interface can not only construct complex hierarchical abstraction, but also construct asynchronous abstraction. In the callback-based asynchronous mode, the synchronization and nesting of multiple asynchronous calls are difficult to use. Sometimes a slightly complicated call and synchronization relationship will cause the code to be full of complicated synchronization checks and layer-by-layer callbacks, which is hard to understand and maintain. This problem is essentially the same as the preceding HTML example because most common languages do not regard Asynchronization as the basic element/semantics, and many asynchronous implementation modes compromise the language. To solve this problem, I wrote an asynchronous DSL Based on smooth interfaces using JavaScript. The sample code is as follows:

//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();
The above code is just a javascript statement, but from another perspective it is like a DSL program that describes asynchronous calls. It defines the syntax structure of whin when end through a smooth interface, followed by the Code for starting asynchronous calls, and the Code for processing asynchronous results after when. You can select each_done, all_done, one or more timeout types. The whin when end structure can be nested. For example, the above Code contains another begin when end structure in the timeout Processing Branch. Through this DSL, we can better express the synchronization and nesting relationships of asynchronous calls than callback-based methods.

The above describes four typical abstractions constructed with smooth interfaces. In addition, there are many other abstractions and application scenarios, such: many unit test frameworks define unit test DSL through smooth interfaces. Although most of the above examples are based on dynamic languages such as JavaScript, the syntax basics of fluent interfaces are not harsh. Even in static languages such as Java, they can be easily used. Unlike traditional API design, the key to understanding and using smooth interfaces is to break through the fixed mindset brought about by the Language abstraction mechanism, and select appropriate abstract dimensions based on the problem domain, use the basic syntax of a language to construct domain-specific semantics and syntax.

Reference

Wikipedia: Fluent Interface

Martin Fowler: Fluent Interface

Jquery is DSL

An approach to internal domain-specific languages in Java

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.