Fluent interface(维基百科)

2016-09-19 10:18:30来源:oschina作者:tantexian人点击

From Wikipedia, the free encyclopedia


Insoftware engineering, afluent interface(as first coined byEric EvansandMartin Fowler) is an implementation of anobject orientedAPI that aims to provide more readable code.


A fluent interface is normally implemented by usingmethod cascading(concretelymethod chaining) to relay the instruction context of a subsequent call (but a fluent interface entails more than just method chaining[1]). Generally, the context is

defined through the return value of a called method
self-referential, where the new context is equivalent to the last context
terminated through the return of a void context.
Contents

[hide]

1History
2Examples

2.1JavaScript
2.2Java
2.3C#
2.4C++
2.5D
2.6Ruby
2.7Scala
2.8Perl 6
2.9PHP
2.10Python3Problems

3.1Debugging & error reporting
3.2Logging
3.3Subclasses4See also
5References
6External links
History[edit]

The term "fluent interface" was coined in late 2005, though this overall style of interface dates to the invention of method cascading in Smalltalk in the 1970s, and numerous examples in the 1980s. A common example is theiostreamlibrary in C++, which uses the<>operatorsfor the message passing, sending multiple data to the same object and allowing "manipulators" for other method calls. Other early examples include theGarnet system(from 1988 in Lisp) and theAmulet system(from 1994 in C++) which used this style for object creation and property assignment.


Examples[edit]
JavaScript[edit]

There are many examples of JavaScript libraries that use some variant of this:jQueryprobably being the most well known. Typically fluent builders are used to implement 'database queries', for example inhttps://github.com/Medium/dynamite:


// getting an item from a table
client.getItem('user-table')
.setHashKey('userId', 'userA')
.setRangeKey('column', '@')
.execute()
.then(function(data) {
// data.result: the resulting object
})

A simple way to do this in JavaScript is using prototype inheritance and `this`.


// example from http://schier.co/post/method-chaining-in-javascript
// define the class
var Kitten = function() {
this.name = 'Garfield';
this.color = 'brown';
this.gender = 'male';
};
Kitten.prototype.setName = function(name) {
this.name = name;
return this;
};
Kitten.prototype.setColor = function(color) {
this.color = color;
return this;
};
Kitten.prototype.setGender = function(gender) {
this.gender = gender;
return this;
};
Kitten.prototype.save = function() {
console.log(
'saving ' + this.name + ', the ' +
this.color + ' ' + this.gender + ' kitten...'
);
// save to database here...
return this;
};
// use it
new Kitten()
.setName('Bob')
.setColor('black')
.setGender('male')
.save();
Java[edit]

ThejOOQlibrary models SQL as a fluent API in Java


Author author = AUTHOR.as("author");
create.selectFrom(author)
.where(exists(selectOne()
.from(BOOK)
.where(BOOK.STATUS.eq(BOOK_STATUS.SOLD_OUT))
.and(BOOK.AUTHOR_ID.eq(author.ID))));

Theop4jlibrary enables the use of fluent code for performing auxiliary tasks like structure iteration, data conversion, filtering, etc.


String[] datesStr = new String[] {"12-10-1492", "06-12-1978"};
...
List dates =
Op.on(datesStr).toList().map(FnString.toCalendar("dd-MM-yyyy")).get();

Theflufluannotation processor enables the creation of a fluent API using Java annotations.


TheJaQuelibrary enables Java 8 Lambdas to be represented as objects in the form ofexpression treesat runtime, making it possible to create type-safe fluent interfaces, i.e. instead of:


Customer obj = ...
obj.property("name").eq("John")

One can write:


method(customer -> customer.getName() == "John")

Also, themock objecttesting libraryEasyMockmakes extensive use of this style of interface to provide an expressive programming interface.


Collection mockCollection = EasyMock.createMock(Collection.class);
EasyMock.expect(mockCollection.remove(null)).andThrow(new NullPointerException()).atLeastOnce();

In the Java Swing API, the LayoutManager interface defines how Container objects can have controlled Component placement. One of the more powerful LayoutManager implementations is the GridBagLayout class which requires the use of the GridBagConstraints class to specify how layout control occurs. A typical example of the use of this class is something like the following.


GridBagLayout gl = new GridBagLayout();
JPanel p = new JPanel();
p.setLayout( gl );
JLabel l = new JLabel("Name:");
JTextField nm = new JTextField(10);
GridBagConstraints gc = new GridBagConstraints();
gc.gridx = 0;
gc.gridy = 0;
gc.fill = GridBagConstraints.NONE;
p.add( l, gc );
gc.gridx = 1;
gc.fill = GridBagConstraints.HORIZONTAL;
gc.weightx = 1;
p.add( nm, gc );

This creates a lot of code and makes it difficult to see what exactly is happening here. The Packer class, visible athttp://java.net/projects/packer/, provides a Fluent mechanism for using this class so that you would instead write:


JPanel p = new JPanel();
Packer pk = new Packer( p );
JLabel l = new JLabel("Name:");
JTextField nm = new JTextField(10);
pk.pack( l ).gridx(0).gridy(0);
pk.pack( nm ).gridx(1).gridy(0).fillx();

There are many places where Fluent APIs can greatly simplify how software is written and help create an API language that helps users be much more productive and comfortable with the API because the return value of a method always provides a context for further actions in that context.


C#[edit]

C# uses fluent programming extensively inLINQto build queries using thestandard query operators. The implementation is based onextension methods.


var translations = new Dictionary
{
{"cat", "chat"},
{"dog", "chien"},
{"fish", "poisson"},
{"bird", "oiseau"}
};
// Find translations for English words containing the letter "a",
// sorted by length and displayed in uppercase
IEnumerable query = translations
.Where (t => t.Key.Contains("a"))
.OrderBy (t => t.Value.Length)
.Select(t => t.Value.ToUpper());
// The same query constructed progressively:
var filtered = translations.Where (t => t.Key.Contains("a"));
var sorted = filtered.OrderBy (t => t.Value.Length);
var finalQuery = sorted.Select(t => t.Value.ToUpper());

Fluent interface can also be used to chain a set of method, which operates/shares the same object. Like instead of creating a customer class we can create a data context which can be decorated with fluent interface as follows.


// Defines the data context
class Context
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Sex { get; set; }
public string Address { get; set; }
}
class Customer
{
private Context _context = new Context(); // Initializes the context
// set the value for properties
public Customer FirstName(string firstName)
{
_context.FirstName = firstName;
return this;
}
public Customer LastName(string lastName)
{
_context.LastName = lastName;
return this;
}
public Customer Sex(string sex)
{
_context.Sex = sex;
return this;
}
public Customer Address(string address)
{
_context.Address = address;
return this;
}
// Prints the data to console
public void Print()
{
Console.WriteLine("First name: {0} /nLast name: {1} /nSex: {2} /nAddress: {3}", _context.FirstName, _context.LastName, _context.Sex, _context.Address);
}
}
class Program
{
static void Main(string[] args)
{
// Object creation
Customer c1 = new Customer();
// Using the method chaining to assign & print data with a single line
c1.FirstName("vinod").LastName("srivastav").Sex("male").Address("bangalore").Print();
}
}
C++[edit]

A common use of the fluent interface inC++is the standardiostream, which chainsoverloaded operators.


The following is an example of providing a fluent interface wrapper on top of a more traditional interface in C++:


// Basic definition
class GlutApp {
private:
int w_, h_, x_, y_, argc_, display_mode_;
char **argv_;
char *title_;
public:
GlutApp(int argc, char** argv) {
argc_ = argc;
argv_ = argv;
}
void setDisplayMode(int mode) {
display_mode_ = mode;
}
int getDisplayMode() {
return display_mode_;
}
void setWindowSize(int w, int h) {
w_ = w;
h_ = h;
}
void setWindowPosition(int x, int y) {
x_ = x;
y_ = y;
}
void setTitle(const char *title) {
title_ = title;
}
void create(){;}
};
// Basic usage
int main(int argc, char **argv) {
GlutApp app(argc, argv);
app.setDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_ALPHA|GLUT_DEPTH); // Set framebuffer params
app.setWindowSize(500, 500); // Set window params
app.setWindowPosition(200, 200);
app.setTitle("My OpenGL/GLUT App");
app.create();
}
// Fluent wrapper
class FluentGlutApp : private GlutApp {
public:
FluentGlutApp(int argc, char **argv) : GlutApp(argc, argv) {} // Inherit parent constructor
FluentGlutApp &withDoubleBuffer() {
setDisplayMode(getDisplayMode() | GLUT_DOUBLE);
return *this;
}
FluentGlutApp &withRGBA() {
setDisplayMode(getDisplayMode() | GLUT_RGBA);
return *this;
}
FluentGlutApp &withAlpha() {
setDisplayMode(getDisplayMode() | GLUT_ALPHA);
return *this;
}
FluentGlutApp &withDepth() {
setDisplayMode(getDisplayMode() | GLUT_DEPTH);
return *this;
}
FluentGlutApp &across(int w, int h) {
setWindowSize(w, h);
return *this;
}
FluentGlutApp &at(int x, int y) {
setWindowPosition(x, y);
return *this;
}
FluentGlutApp &named(const char *title) {
setTitle(title);
return *this;
}
// It doesn't make sense to chain after create(), so don't return *this
void create() {
GlutApp::create();
}
};
// Fluent usage
int main(int argc, char **argv) {
FluentGlutApp(argc, argv)
.withDoubleBuffer().withRGBA().withAlpha().withDepth()
.at(200, 200).across(500, 500)
.named("My OpenGL/GLUT App")
.create();
}
D[edit]

Because of the Uniform Function Call Syntax (UFCS) inD,[2]method chaining is particularly easy. If you write


x.toInt();

and the type ofxdoes not provide atoInt()member function, then the compiler looks for a free function of the form


toInt(x);

This enables chaining methods in a fluent way like this


x.toInt().toString(format);

instead of this


toString(toInt(x),format);
Ruby[edit]

TheRubylanguage allows modifications to core classes. This enables a programmer to implement fluent interfaces natively.


In Ruby strings are instances of a String class, by defining new methods to the String class which each returns strings, we natively allow chaining of methods. In the example below, we define three new methods: indent, prefix and suffix. Each returning a string and hence an instance of String that has the three defined methods.


# Add methods to String class
class String
def prefix(raw)
"#{raw} #{self}"
end
def suffix(raw)
"#{self} #{raw}"
end
def indent(raw)
raw = " " * raw if raw.kind_of? Fixnum
prefix(raw)
end
end# Fluent interface
message = "there"
puts message.prefix("hello")
.suffix("world")
.indent(8)
Scala[edit]

Scala supports a fluent syntax for both method calls and class mixins, usingtraits and thewithkeyword. For example:


class Color { def rgb(): Tuple3[Decimal] }
object Black extends Color { override def rgb(): Tuple3[Decimal] = ("0", "0", "0"); }
trait GUIWindow {
// Rendering methods that return this for fluent drawing
def set_pen_color(color: Color): this.type
def move_to(pos: Position): this.type
def line_to(pos: Position, end_pos: Position): this.type
def render(): this.type = this // Don't draw anything, just return this, for child implementations to use fluently
def top_left(): Position
def bottom_left(): Position
def top_right(): Position
def bottom_right(): Position
}
trait WindowBorder extends GUIWindow {
def render(): GUIWindow = {
super.render()
.move_to(top_left())
.set_pen_color(Black)
.line_to(top_right())
.line_to(bottom_right())
.line_to(bottom_left())
.line_to(top_left())
}
}
class SwingWindow extends GUIWindow { ... }
val appWin = new SwingWindow() with WindowBorder
appWin.render()
Perl 6[edit]

InPerl 6, there are many approaches, but one of the simplest is to declare attributes as read/write and use thegivenkeyword. The type annotations are optional, but the nativegradual typingmakes it much safer to write directly to public attributes.


class Employee {
subset Salaryof Real where * > 0;
subset NonEmptyString of Strwhere * ~~ //S/; # at least one non-space character
has NonEmptyString $.nameis rw;
has NonEmptyString $.surname is rw;
has Salary$.salaryis rw;
method gist {
return qq:to[END];
Name:$.name
Surname: $.surname
Salary:$.salary
END
}
}
my $employee = Employee.new();
given $employee {
.name= 'Sally';
.surname = 'Ride';
.salary= 200;
}
say $employee;
# Output:
# Name:Sally
# Surname: Ride
# Salary:200
PHP[edit]

In PHP, one can return the current object by using the $this special variable which represent the instance. Hencereturn $this;will make the method return the instance. The example below defines a class Employee and three methods to set its name, surname and salary. Each return the instance of the Employee class allowing to chain methods.


<?php
class Employee
{
public $name;
public $surName;
public $salary;
public function setName($name)
{
$this->name = $name;
return $this;
}
public function setSurname($surname)
{
$this->surName = $surname;
return $this;
}
public function setSalary($salary)
{
$this->salary = $salary;
return $this;
}
public function __toString()
{
$employeeInfo = 'Name: ' . $this->name . PHP_EOL;
$employeeInfo .= 'Surname: ' . $this->surName . PHP_EOL;
$employeeInfo .= 'Salary: ' . $this->salary . PHP_EOL;
return $employeeInfo;
}
}
# Create a new instance of the Employee class, Tom Smith, with a salary of 100:
$employee = (new Employee())
->setName('Tom')
->setSurname('Smith')
->setSalary('100');
# Display the value of the Employee instance:
echo $employee;
# Display:
# Name: Tom
# Surname: Smith
# Salary: 100
Python[edit]

In Python returning `self` in the instance method is one way to implement the fluent pattern.


class Poem(object):
def __init__(self, content):
self.content = content
def indent(self, spaces):
self.content = " " * spaces + self.content
return self
def suffix(self, content):
self.content = self.content + " - " + content
return self
>>> Poem("Road Not Travelled").indent(4).suffix("Robert Frost").content
'Road Not Travelled - Robert Frost'
Problems[edit]
Debugging & error reporting[edit]

Single-line chained statements may be more difficult to debug as debuggers may not be able to set breakpoints within the chain. Stepping through a single-line statement in a debugger may also be less convenient.


java.nio.ByteBuffer.allocate(10).rewind().limit(100);

Another issue is that it may not be clear which of the method calls caused an exception, in particular if there are multiple calls to the same method. These issues can be overcome by breaking the statement into multiple lines which preserves readability while allowing the user to set breakpoints within the chain and to easily step through the code line by line:


java.nio.ByteBuffer
.allocate(10)
.rewind()
.limit(100);

However, some debuggers always show the first line in the exception backtrace, although the exception has been thrown on any line.


Logging[edit]

One more issue is with adding log statements.


ByteBuffer buffer = ByteBuffer.allocate(10).rewind().limit(100);

E.g. to log the state ofbufferafterrewind() method call, it is necessary to break the fluent calls:


ByteBuffer buffer = ByteBuffer.allocate(10).rewind();
log.debug("First byte after rewind is " + buffer.get(0));
buffer.limit(100);
Subclasses[edit]

Subclasses instrongly typedlanguages (C++, Java, C#, etc.) often have to override all methods from their superclass that participate in a fluent interface in order to change their return type. For example, in Java:


class A {
public A doThis() { ... }
}
class B extends A{
public A doThis() { super.doThis(); return this; } // Must change return type to B.
public B doThat() { ... }
}
...
A a = new B().doThat().doThis(); // It works even without overriding A.doThis().
B b = new B().doThis().doThat(); // It would fail without overriding A.doThis().

Languages that are capable of expressingF-bound polymorphismcan use it to avoid this difficulty. E. g. in Java:


abstract class AbstractA> {
@SuppressWarnings("unchecked")
public T doThis() { ...; return (T)this; }
}
class A extends AbstractA {}

class B extends AbstractA {
public B doThat() { ...; return this; }
}
...
B b = new B().doThis().doThat(); // Works!
A a = new A().doThis(); // Also works.

Note that in order to be able to create instances of the parent class, we had to split it into two classes —AbstractAandA, the latter with no content (it would only contain constructors if those were needed). The approach can easily be extended if we want to have sub-subclasses (etc.) too:


abstract class AbstractB> extends AbstractA {
@SuppressWarnings("unchecked")
public T doThat() { ...; return (T)this; }
}
class B extends AbstractB {}
abstract class AbstractC> extends AbstractB {
@SuppressWarnings("unchecked")
public T foo() { ...; return (T)this; }
}
class C extends AbstractC {}
...
C c = new C().doThis().doThat().foo(); // Works!
B b = new B().doThis().doThat();// Still works.
See also[edit]Command-query separation
Method chaining
Pipeline (Unix) References[edit]Jump up^/2014th7cj/d/file/p/20160918/kmvc33utvmm.html
Jump up^Uniform Function Call Syntax, Dr. Dobbs Journal, 28 Mar 2012 External links[edit]Martin Fowler's original bliki entry coining the term
A Delphi example of writing XML with a fluent interface
A .NET fluent validation library written in C#
A tutorial for creating formal Java fluent APIs from a BNF notation

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台