Jump to content

Fluent interface: Difference between revisions

From Wikipedia, the free encyclopedia
Content deleted Content added
→‎Examples: adding C# example, based on Chapter 8 of c# 4.0 in a nutshell
Line 210: Line 210:
val appWin = new SwingWindow() with WindowBorder;
val appWin = new SwingWindow() with WindowBorder;
appWin.render()
appWin.render()
</source>
===C#===

C# uses fluent programming extensively in [[LINQ]] to build queries using the ''standard query operators''.

<source lang="csharp">

Dictionary<string, string> translations = new Dictionary<string, string>
{
{"cat", "chat"},
{"dog", "chien",
{"fish", "poisson"},
{"bird", "oiseau"}
};

// find translations for english words containing letter "a",
// sorted by length and displayed in uppercase
IEnumerable<string> query = translations
.Where (t => t.Key.Contains ("a"))
.OrderBy (t => t.Value.Length)
.Select (t => t.Value.ToUpper());

// The same query constructed progressively:

IEnumerable<string> filtered = translations.Where (t => t.Key.Contains ("a"));
IEnumerable<string> sorted = filtered.OrderBy (t => t.Value.Length);
IEnumerable<string> finalQuery = sorted.Select (t => t.Value.ToUpper());
</source>
</source>



Revision as of 11:05, 5 July 2013

In software engineering, 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 [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.

Examples

Java

The op4j library 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<Calendar> dates = 
    Op.on(datesStr).toList().map(FnString.toCalendar("dd-MM-yyyy")).get();

The fluflu annotation processor enables to create fluent API using annotations on the Java code.

Also, the mock object testing library EasyMock makes 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 at http://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++

A common use of the fluent interface in C++ is the standard iostream, which chains overloaded 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();
 }

Ruby

The Ruby language allows modifications to core classes. This enables a programmer to implement fluent interfaces natively.

class String
  def indent(sraw)
    if(sraw.class == Fixnum)
      sraw = " " * sraw;
    end
    self.to_s.gsub(/^/,sraw);
  end
  def prefix(sraw) sraw + self.to_s end
  def suffix(sraw) [self.to_s,sraw].join('') end
end
	 
## Fluent interface
smessage = "there";   ## may also be written smessage = String.new("there");
smessage = smessage.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[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): GUIWindow;
   def move_to(pos: Position): GUIWindow;
   def line_to(pos: Position, end_pos: Position): GUIWindow;

   def render(): GUIWindow = { 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()

C#

C# uses fluent programming extensively in LINQ to build queries using the standard query operators.

Dictionary<string, string> translations = new Dictionary<string, string>
                                          { 
                                              {"cat", "chat"}, 
                                              {"dog", "chien", 
                                              {"fish", "poisson"},
                                              {"bird", "oiseau"}
                                          };

// find translations for english words containing letter "a", 
// sorted by length and displayed in uppercase
IEnumerable<string> query = translations
	.Where   (t => t.Key.Contains ("a"))
	.OrderBy (t => t.Value.Length)
	.Select  (t => t.Value.ToUpper());

// The same query constructed progressively:

IEnumerable<string> filtered   = translations.Where (t => t.Key.Contains ("a"));
IEnumerable<string> sorted     = filtered.OrderBy   (t => t.Value.Length);
IEnumerable<string> finalQuery = sorted.Select      (t => t.Value.ToUpper());

Problems

Debugging & Error reporting

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 compilers always show the first line in the exception backtrace, although the exception has been thrown on any line.

Logging

One more issue is with adding log statements.

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

E.g. to log a the state of buffer after rewind() 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

Subclasses in strong typed languages (C++, Java, C#, ...) have to override all methods in their superclass that participate in fluent interface, changing their return type. For example, in Java:

public class A {
    public A doThis() { ... }
}
public class B extends A{
    public B doThis() { super.doThis(); } // 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().

See also

References

External links