Fluent interface

Fluent interface

Текучий интерфейс (англ. fluent interface, название придумано Эриком Эвансом и Мартином Фаулером) — способ реализации, в разработке программного обеспечения, объектно-ориентированного API, нацеленный на повышение читабельности исходного кода программы.

Текучий интерфейс хорош тем, что упрощается множественный вызов методов одного объекта. Обычно это реализуется использованием цепочки методов, передающих контекст вызова следующему звену (но текучий интерфейс влечет за собой нечто большее, чем просто цепочку методов [1]). Обычно, этот контекст:

  • определен с помощью значения, возвращаемого методом;
  • наследуется (в качестве нового контекста используется предыдущий);
  • прекращается возвращением ничего не значащего значения (void).

Такой стиль косвенно полезен повышением наглядности и интуитивности кода[источник не указан 599 дней]. Однако может весьма пагубно сказаться на отладке, если цепочка действует как одно выражение, куда отладчик не всегда может установить промежуточную точку останова.

Содержание

Примеры

Delphi (Object Pascal)

Следующий пример показывает обычный класс и класс, реализующий текучий интерфейс, и различия в использовании. Пример написан на Delphi Object Pascal:

unit FluentInterface;
 
interface
 
type
  IConfiguration = interface
    procedure SetColor(Color: string);
    procedure SetHeight(height: integer);
    procedure SetLength(length: integer);
    procedure SetDepth(depth: integer);
  end;
 
  IConfigurationFluent = interface
    function SetColor(Color: string): IConfigurationFluent;
    function SetHeight(height: integer): IConfigurationFluent;
    function SetLength(length: integer): IConfigurationFluent;
    function SetDepth(depth: integer): IConfigurationFluent;
  end;
 
  TConfiguration = class(TInterfacedObject, IConfiguration)
  private
    FColor: string;
    FHeight: integer;
    FLength: integer;
    FDepth: integer;
  protected
    procedure SetColor(Color: string);
    procedure SetHeight(height: integer);
    procedure SetLength(length: integer);
    procedure SetDepth(depth: integer);
  end;
 
  TConfigurationFluent = class(TInterfacedObject, IConfigurationFluent)
  private
    FColor: string;
    FHeight: integer;
    FLength: integer;
    FDepth: integer;
  protected
    function SetColor(Color: string): IConfigurationFluent;
    function SetHeight(height: integer): IConfigurationFluent;
    function SetLength(length: integer): IConfigurationFluent;
    function SetDepth(depth: integer): IConfigurationFluent;
  public
    class function New: IConfigurationFluent;
  end;
 
implementation
 
procedure TConfiguration.SetColor(Color: string);
begin
  FColor := Color;
end;
 
procedure TConfiguration.SetDepth(depth: integer);
begin
  FDepth := depth;
end;
 
procedure TConfiguration.SetHeight(height: integer);
begin
  FHeight := height;
end;
 
procedure TConfiguration.SetLength(length: integer);
begin
  FLength := length;
end;
 
class function TConfigurationFluent.New: IConfigurationFluent;
begin
  Result := Create;
end;
 
function TConfigurationFluent.SetColor(Color: string): IConfigurationFluent;
begin
  FColor := Color;
  Result := Self;
end;
 
function TConfigurationFluent.SetDepth(depth: integer): IConfigurationFluent;
begin
  FDepth := depth;
  Result := Self;
end;
 
function TConfigurationFluent.SetHeight(height: integer): IConfigurationFluent;
begin
  FHeight := height;
  Result := Self;
end;
 
function TConfigurationFluent.SetLength(length: integer): IConfigurationFluent;
begin
  FLength := length;
  Result := Self;
end;
 
end.
var C, D: IConfiguration;
    E: IConfigurationFluent;
begin
  { Обычное использование:}
  C := TConfiguration.Create;
  C.SetColor('blue');
  C.SetHeight(1);
  C.SetLength(2);
  C.SetDepth(3);
 
  { обычная реализация, упрощенная с помощью инструкции with }
  D := TConfiguration.Create;
  with D do begin
    SetColor('blue');
    SetHeight(1);
    SetLength(2);
    SetDepth(3)
  end;
 
  { использование реализации с текучим интерфейсом }
  E := TConfigurationFluent.New
       .SetColor('Blue')
       .SetHeight(1)
       .SetLength(2)
       .SetDepth(3);
end;

C#

Начиная с C# 3.5 и выше введены продвинутые способы реализации текучего интерфейса:

namespace Example.FluentInterfaces
{
    #region Standard Example
 
    public interface IConfiguration
    {
        string Color { set; }
        int Height { set; }
        int Length { set; }
        int Depth { set; }
    }
 
    public class Configuration : IConfiguration
    {
        string color;
        int height;
        int length;
        int depth;
 
        public string Color
        {
            set { color = value; }
        }
 
        public int Height
        {
            set { height = value; }
        }
 
        public int Length
        {
            set { length = value; }
        }
 
        public int Depth
        {
            set { depth = value; }
        }
    }
 
    #endregion
 
    #region Fluent Example
 
    public interface IConfigurationFluent
    {
        IConfigurationFluent SetColor(string color);
        IConfigurationFluent SetHeight(int height);
        IConfigurationFluent SetLength(int length);
        IConfigurationFluent SetDepth(int depth);
    }
 
    public class ConfigurationFluent : IConfigurationFluent
    {
        string color;
        int height;
        int length;
        int depth;
 
        public IConfigurationFluent SetColor(string color)
        {
            this.color = color;
            return this;
        }
 
        public IConfigurationFluent SetHeight(int height)
        {
            this.height = height;
            return this;
        }
 
        public IConfigurationFluent SetLength(int length)
        {
            this.length = length;
            return this;
        }
 
        public IConfigurationFluent SetDepth(int depth)
        {
            this.depth = depth;
            return this;
        }
    }
 
    #endregion
 
    public class ExampleProgram
    {
        public static void Main(string[] args)
        {
            // Обычный пример
            IConfiguration config = new Configuration
            {
                Color = "blue",
                Height = 1,
                Length = 2,
                Depth = 3
            };
 
            // Пример текучего интерфейса
            IConfigurationFluent fluentConfig =
                  new ConfigurationFluent().SetColor("blue")
                                           .SetHeight(1)
                                           .SetLength(2)
                                           .SetDepth(3);
        }
    }
}

C++

Банальный пример в C++ — стандартный iostream, где текучесть обеспечивается перегрузкой операторов.

Пример обертки текучего интерфейса в C++:

 // обычное задание
 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();
 };
 // обычное использование
 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();
 }
 
 // Обертка текучего интерфейса
 class FluentGlutApp : private GlutApp {
 public:
     FluentGlutApp(int argc, char **argv) : GlutApp(argc, argv) {} // наследуем родительский конструктор
     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;
     }
     // без разницы, вести ли цепь после вызова create(), поэтому не возвращаем *this
     void create() {
         GlutApp::create();
     }
 };
 // используем текучий интерфейс
 int main(int argc, char **argv) {
     FluentGlutApp app(argc, argv)
         .withDoubleBuffer().withRGBA().withAlpha().withDepth()
         .at(200, 200).across(500, 500)
         .named("My OpenGL/GLUT App");
     app.create();
 }

Java

Некоторые API в Java реализуют такой интерфейс, например 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();
}


Библиотека op4j позволяет использовать текучий интерфейс для выполнения вспомогательных задач, вроде итерирования структур, конвертирования информации, фильтрации, и т. д.

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


Также, библиотека Mock-объект тестирования EasyMock активно использует этот стиль для предоставления удобного интерфейса.

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

PHP

Пример реализации класса с текучим интерфейсом в PHP:

class Car {
        private $speed;
        private $color;
        private $doors;
 
        public function setSpeed($speed){
                $this->speed = $speed;
                return $this;
        }
 
        public function setColor($color) {
                $this->color = $color;
                return $this;
        }
 
        public function setDoors($doors) {
                $this->doors = $doors;
                return $this;
        }
}
 
// Обычная реализация
$myCar2 = new Car();
$myCar2->setSpeed(100);
$myCar2->setColor('blue');
$myCar2->setDoors(5);
 
// Текучий интерфейс
$myCar = new Car();
$myCar->setSpeed(100)->setColor('blue')->setDoors(5);

JavaScript

Пример реализации класса с текучим интерфейсом в JavaScript:

var Car = (function(){
 
        var speed, color, doors, pub;
 
        function setSpeed(new_speed) {
                speed = new_speed;
                return pub;
        }
 
        function setColor(new_color) {
                color = new_color;
                return pub;
        }
 
        function setDoors(new_doors) {
                doors = new_doors;
                return pub;
        }
 
        pub = {
                'setSpeed': setSpeed,
                'setColor': setColor,
                'setDoors': setDoors,
        };
 
        return pub;
 
})
 
// Обычная реализация
myCar2 = Car();
myCar2.setSpeed(100);
myCar2.setColor('blue');
myCar2.setDoors(5);
 
// Текучий интерфейс
myCar = Car();
myCar.setSpeed(100).setColor('blue').setDoors(5);

Также можно использовать иной подход:

var $ = function(selector) {
    if(this.$) {
        return new $(selector);
    }
    if(typeof selector == "string") {
        this.init = document.getElementById(selector);
    }
};
 
$.prototype = {
    text: function(text) {
        if(!text){
           this.init.innerHTML;
        }
        this.init.innerHTML = text;
        return this;
    },
    css: function(style) {
        for(var i in style){
           this.init.style[i] = style[i];
        }
        return this;
    }
};
//пример использования:
$('div').text('div').css({color: "red"});

Пример независящей от типа возвращаемого объекта реализации:

({
    foo: function (a) {
        return a;
    }
}).foo('foo').toUpperCase();

Примечания

Ссылки


Wikimedia Foundation. 2010.

Игры ⚽ Нужен реферат?

Полезное


Смотреть что такое "Fluent interface" в других словарях:

  • Fluent Interface — Fluent Interfaces[1] stellen in der Software Entwicklung eine Art Programmierschnittstelle dar, die es ermöglicht, beinahe in Satzform Funktionalitäten aufzurufen. Der daraus resultierende Programmcode ist leicht lesbar und macht somit das… …   Deutsch Wikipedia

  • Fluent interface — In software engineering, a fluent interface (as first coined by Eric Evans and Martin Fowler) is an object oriented construct that defines a behavior capable of relaying the instruction context of a subsequent call. Generally, the context is *… …   Wikipedia

  • Fluent — can refer to:* fluency, in linguistics, the ability to communicate quickly. * fluent (mathematics), in mathematics, a continuous function * fluent (artificial intelligence), in artificial intelligence, a condition that varies over time * Fluent,… …   Wikipedia

  • Fluent — Siège de la société …   Wikipédia en Français

  • Multiple frames interface — A Multiple Frames Interface (MFI) is a type of user interface which displays information in a set of frames or panels that can be scrolled vertically on the screen. It is an integrated interface designed to bring together virtually all the… …   Wikipedia

  • Désignation chaînée — Fluent Interface / Interface fluide En génie logiciel, une désignation chaînée ou chaînage de méthodes consiste à agir en une seule instruction sur plusieurs méthodes du même objet, dans un but de plus grande lisibilité. L idée principale est qu… …   Wikipédia en Français

  • Moose (analysis) — Moose Screenshot  …   Wikipedia

  • Ribbon — В этой статье не хватает ссылок на источники информации. Информация должна быть проверяема, иначе она может быть поставлена под сомнение и удалена. Вы можете отредактировать эту стать …   Википедия

  • Design Pattern — Entwurfsmuster (engl. design pattern) sind bewährte Lösungs Schablonen für wiederkehrende Entwurfsprobleme der Softwarearchitektur und Softwareentwicklung. Sie stellen damit eine wiederverwendbare Vorlage zur Problemlösung dar, die in einem… …   Deutsch Wikipedia

  • Design Patterns — Entwurfsmuster (engl. design pattern) sind bewährte Lösungs Schablonen für wiederkehrende Entwurfsprobleme der Softwarearchitektur und Softwareentwicklung. Sie stellen damit eine wiederverwendbare Vorlage zur Problemlösung dar, die in einem… …   Deutsch Wikipedia


Поделиться ссылкой на выделенное

Прямая ссылка:
Нажмите правой клавишей мыши и выберите «Копировать ссылку»