Fluent interface

From Wikipedia, the free encyclopedia

Jump to: navigation, search

In software engineering, a fluent interface (as first coined by Eric Evans and Martin Fowler) is a way of implementing an object oriented API in a way that provides 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.

This style is beneficial due to its ability to provide a more fluid feel to the code.

[edit] Examples

The following example shows a class implementing a non-fluent interface, another class implementing a fluent counterpart of the aforementioned non-fluent interface, and differences in usage. The example is written in C#:

 namespace Example.FluentInterfaces
 {
    using System;
    public interface IConfiguration
    {
        void SetColor(string color);
        void SetHeight(int height);
        void SetLength(int length);
        void SetDepth(int depth);
    }
 
    public interface IConfigurationFluent
    {
        IConfigurationFluent SetColor(string color);
        IConfigurationFluent SetHeight(int height);
        IConfigurationFluent SetLength(int length);
        IConfigurationFluent SetDepth(int depth);
    }
 
    public class Configuration : IConfiguration
    {
        string color;
        int height;
        int length;
        int depth;
        void SetColor(string color)
        {
           this.color = color;
        }
        void SetHeight(int height)
        {
           this.height = height;
        }
        void SetLength(int length)
        {
           this.length = length;
        }
        void SetDepth(int depth)
        {
           this.depth = depth;
        }
    }
 
    public class ConfigurationFluent : IConfigurationFluent
    {
        string color;
        int height;
        int length;
        int depth;
        IConfigurationFluent SetColor(string color)
        {
           this.color = color;
           return this;
        }
        IConfigurationFluent SetHeight(int height)
        {
           this.height = height;
           return this;
        }
        IConfigurationFluent SetLength(int length)
        {
           this.length = length;
           return this;
        }
        IConfigurationFluent SetDepth(int depth)
        {
           this.depth = depth;
           return this;
        }
    }
 
    public class ExampleProgram
    {
        [STAThread]
        public static void Main(string[] args)
        {
            //Standard Example
            IConfiguration config = new Configuration();
            config.SetColor("blue");
            config.SetHeight(1);
            config.SetLength(2);
            config.SetDepth(3);
            //FluentExample
            IConfigurationFluent config = 
                  new ConfigurationFluent().SetColor("blue")
                                           .SetHeight(1)
                                           .SetLength(2)
                                           .SetDepth(3);
        }
    }
 }

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;
     }
     void 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();
     }
 };
 // basic usage
 int main(int argc, char **argv) {
     FluentGlutApp app(argc, argv)
         .withDouble().withRGBA().withAlpha().withDepth()
         .at(200, 200).across(500, 500)
         .named("My OpenGL/GLUT App");
     app.create();
 }

Some APIs in Java follows this pratice, like Java Persistence API:

public Collection<Student> findByNameAgeGender(String name, int age, Gender gender) {
    return em.createNamedQuery("Student.findByNameAgeGender")
             .setParameter("name", name)
             .setParameter("age", age)
             .setParameter("gender", gender)
             .setFirstResult(1)
             .setMaxResults(30)
             .setHint("hintName", "hintValue")
             .getResultList();
}

[edit] See also

[edit] External links

Personal tools
Languages