流式接口

✍ dations ◷ 2025-03-06 17:08:21 #流式接口

流式接口(fluent interface)是软件工程中面向对象API的一种实现方式,以提供更为可读的源代码。最早由Eric Evans(英语:Eric Evans (technologist))与Martin Fowler于2005年提出。

通常采取方法瀑布调用(英语:enmethod cascading) (具体说是方法链式调用(英语:method chaining))来转发一系列对象方法调用的上下文 。这个上下文(context)通常是指:

C++的iostream流式调用就是一个典型的例子。Smalltalk在1970年代就实现了方法瀑布调用(英语:enmethod cascading)。

用于数据库查询的jQuery,例如https://github.com/Medium/dynamite :

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

JavaScript使用原型继承与`this`.

// example from http://schier.co/post/method-chaining-in-javascript// define the classvar 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 itnew Kitten()  .setName('Bob')  .setColor('black')  .setGender('male')  .save();

Java

jOOQ库模拟了SQL

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

C#

C#在LINQ中大量使用 与扩展方法。

var translations = new Dictionary<string, string>                   {                       {"cat", "chat"},                       {"dog", "chien"},                       {"fish", "poisson"},                       {"bird", "oiseau"}                   };// Find translations for English words containing the letter "a",// sorted by length and displayed in uppercaseIEnumerable<string> 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());

流式接口可用于一系列方法,他们运行在同一对象上。

// Defines the data contextclass 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++

下述代码对比了传统的风格与流式接口的实现风格:

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

Ruby

Ruby语言允许修改核心类,这使得流式接口成为原生易于实现。

# Add methods to String classclass 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)  endend # Fluent interfacemessage = "there"puts message.prefix("hello")            .suffix("world")            .indent(8)

Scala

Scala supports a fluent syntax for both method calls and class mixins, using traits and the with keyword. For example:

class Color { def rgb(): Tuple3 }object Black extends Color { override def rgb(): Tuple3 = ("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 WindowBorderappWin.render()

Perl 6

In Perl 6, there are many approaches, but one of the simplest is to declare attributes as read/write and use the given keyword. The type annotations are optional, but the native gradual typing makes it much safer to write directly to public attributes.

class Employee {    subset Salary         of Real where * > 0;    subset NonEmptyString of Str  where * ~~ /S/; # at least one non-space character    has NonEmptyString $.name    is rw;    has NonEmptyString $.surname is rw;    has Salary         $.salary  is rw;    method gist {        return qq:to;        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

在PHP中,可以使用表示实例的特殊变量$this返回当前对象。因此返回$this将使方法返回实例。下面的示例定义了一个Employee类和三个方法来设置它的名称、姓和薪水。每个Employee类的实例允许调用这些方法。

<?phpclass 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

Python通过在实例方法中返回`self`:

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'



相关