Fluent interface

Поделись знанием:
Перейти к: навигация, поиск

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

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

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

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





Примеры

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


Библиотека [www.op4j.org 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.org/ 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();

Напишите отзыв о статье "Fluent interface"

Примечания

  1. [www.martinfowler.com/bliki/FluentInterface.html MF Bliki: FluentInterface]

Ссылки

  • [martinfowler.com/bliki/FluentInterface.html Martin Fowler’s original bliki entry coining the term] (англ.)
  • [17slon.com/blogs/gabr/2009/04/fluent-xml-1.html A Delphi example of writing XML with a fluent interface] (англ.)
  • [tnvalidate.codeplex.com/ A .NET Fluent validation library written in C#] (англ.)

Отрывок, характеризующий Fluent interface

Мужчина в обтянутых панталонах пропел один, потом пропела она. Потом оба замолкли, заиграла музыка, и мужчина стал перебирать пальцами руку девицы в белом платье, очевидно выжидая опять такта, чтобы начать свою партию вместе с нею. Они пропели вдвоем, и все в театре стали хлопать и кричать, а мужчина и женщина на сцене, которые изображали влюбленных, стали, улыбаясь и разводя руками, кланяться.
После деревни и в том серьезном настроении, в котором находилась Наташа, всё это было дико и удивительно ей. Она не могла следить за ходом оперы, не могла даже слышать музыку: она видела только крашеные картоны и странно наряженных мужчин и женщин, при ярком свете странно двигавшихся, говоривших и певших; она знала, что всё это должно было представлять, но всё это было так вычурно фальшиво и ненатурально, что ей становилось то совестно за актеров, то смешно на них. Она оглядывалась вокруг себя, на лица зрителей, отыскивая в них то же чувство насмешки и недоумения, которое было в ней; но все лица были внимательны к тому, что происходило на сцене и выражали притворное, как казалось Наташе, восхищение. «Должно быть это так надобно!» думала Наташа. Она попеременно оглядывалась то на эти ряды припомаженных голов в партере, то на оголенных женщин в ложах, в особенности на свою соседку Элен, которая, совершенно раздетая, с тихой и спокойной улыбкой, не спуская глаз, смотрела на сцену, ощущая яркий свет, разлитый по всей зале и теплый, толпою согретый воздух. Наташа мало по малу начинала приходить в давно не испытанное ею состояние опьянения. Она не помнила, что она и где она и что перед ней делается. Она смотрела и думала, и самые странные мысли неожиданно, без связи, мелькали в ее голове. То ей приходила мысль вскочить на рампу и пропеть ту арию, которую пела актриса, то ей хотелось зацепить веером недалеко от нее сидевшего старичка, то перегнуться к Элен и защекотать ее.
В одну из минут, когда на сцене всё затихло, ожидая начала арии, скрипнула входная дверь партера, на той стороне где была ложа Ростовых, и зазвучали шаги запоздавшего мужчины. «Вот он Курагин!» прошептал Шиншин. Графиня Безухова улыбаясь обернулась к входящему. Наташа посмотрела по направлению глаз графини Безуховой и увидала необыкновенно красивого адъютанта, с самоуверенным и вместе учтивым видом подходящего к их ложе. Это был Анатоль Курагин, которого она давно видела и заметила на петербургском бале. Он был теперь в адъютантском мундире с одной эполетой и эксельбантом. Он шел сдержанной, молодецкой походкой, которая была бы смешна, ежели бы он не был так хорош собой и ежели бы на прекрасном лице не было бы такого выражения добродушного довольства и веселия. Несмотря на то, что действие шло, он, не торопясь, слегка побрякивая шпорами и саблей, плавно и высоко неся свою надушенную красивую голову, шел по ковру коридора. Взглянув на Наташу, он подошел к сестре, положил руку в облитой перчатке на край ее ложи, тряхнул ей головой и наклонясь спросил что то, указывая на Наташу.
– Mais charmante! [Очень мила!] – сказал он, очевидно про Наташу, как не столько слышала она, сколько поняла по движению его губ. Потом он прошел в первый ряд и сел подле Долохова, дружески и небрежно толкнув локтем того Долохова, с которым так заискивающе обращались другие. Он, весело подмигнув, улыбнулся ему и уперся ногой в рампу.
– Как похожи брат с сестрой! – сказал граф. – И как хороши оба!
Шиншин вполголоса начал рассказывать графу какую то историю интриги Курагина в Москве, к которой Наташа прислушалась именно потому, что он сказал про нее charmante.
Первый акт кончился, в партере все встали, перепутались и стали ходить и выходить.
Борис пришел в ложу Ростовых, очень просто принял поздравления и, приподняв брови, с рассеянной улыбкой, передал Наташе и Соне просьбу его невесты, чтобы они были на ее свадьбе, и вышел. Наташа с веселой и кокетливой улыбкой разговаривала с ним и поздравляла с женитьбой того самого Бориса, в которого она была влюблена прежде. В том состоянии опьянения, в котором она находилась, всё казалось просто и естественно.
Голая Элен сидела подле нее и одинаково всем улыбалась; и точно так же улыбнулась Наташа Борису.
Ложа Элен наполнилась и окружилась со стороны партера самыми знатными и умными мужчинами, которые, казалось, наперерыв желали показать всем, что они знакомы с ней.
Курагин весь этот антракт стоял с Долоховым впереди у рампы, глядя на ложу Ростовых. Наташа знала, что он говорил про нее, и это доставляло ей удовольствие. Она даже повернулась так, чтобы ему виден был ее профиль, по ее понятиям, в самом выгодном положении. Перед началом второго акта в партере показалась фигура Пьера, которого еще с приезда не видали Ростовы. Лицо его было грустно, и он еще потолстел, с тех пор как его последний раз видела Наташа. Он, никого не замечая, прошел в первые ряды. Анатоль подошел к нему и стал что то говорить ему, глядя и указывая на ложу Ростовых. Пьер, увидав Наташу, оживился и поспешно, по рядам, пошел к их ложе. Подойдя к ним, он облокотился и улыбаясь долго говорил с Наташей. Во время своего разговора с Пьером, Наташа услыхала в ложе графини Безуховой мужской голос и почему то узнала, что это был Курагин. Она оглянулась и встретилась с ним глазами. Он почти улыбаясь смотрел ей прямо в глаза таким восхищенным, ласковым взглядом, что казалось странно быть от него так близко, так смотреть на него, быть так уверенной, что нравишься ему, и не быть с ним знакомой.
Во втором акте были картины, изображающие монументы и была дыра в полотне, изображающая луну, и абажуры на рампе подняли, и стали играть в басу трубы и контрабасы, и справа и слева вышло много людей в черных мантиях. Люди стали махать руками, и в руках у них было что то вроде кинжалов; потом прибежали еще какие то люди и стали тащить прочь ту девицу, которая была прежде в белом, а теперь в голубом платье. Они не утащили ее сразу, а долго с ней пели, а потом уже ее утащили, и за кулисами ударили три раза во что то металлическое, и все стали на колена и запели молитву. Несколько раз все эти действия прерывались восторженными криками зрителей.
Во время этого акта Наташа всякий раз, как взглядывала в партер, видела Анатоля Курагина, перекинувшего руку через спинку кресла и смотревшего на нее. Ей приятно было видеть, что он так пленен ею, и не приходило в голову, чтобы в этом было что нибудь дурное.
Когда второй акт кончился, графиня Безухова встала, повернулась к ложе Ростовых (грудь ее совершенно была обнажена), пальчиком в перчатке поманила к себе старого графа, и не обращая внимания на вошедших к ней в ложу, начала любезно улыбаясь говорить с ним.
– Да познакомьте же меня с вашими прелестными дочерьми, – сказала она, – весь город про них кричит, а я их не знаю.
Наташа встала и присела великолепной графине. Наташе так приятна была похвала этой блестящей красавицы, что она покраснела от удовольствия.
– Я теперь тоже хочу сделаться москвичкой, – говорила Элен. – И как вам не совестно зарыть такие перлы в деревне!
Графиня Безухая, по справедливости, имела репутацию обворожительной женщины. Она могла говорить то, чего не думала, и в особенности льстить, совершенно просто и натурально.
– Нет, милый граф, вы мне позвольте заняться вашими дочерьми. Я хоть теперь здесь не надолго. И вы тоже. Я постараюсь повеселить ваших. Я еще в Петербурге много слышала о вас, и хотела вас узнать, – сказала она Наташе с своей однообразно красивой улыбкой. – Я слышала о вас и от моего пажа – Друбецкого. Вы слышали, он женится? И от друга моего мужа – Болконского, князя Андрея Болконского, – сказала она с особенным ударением, намекая этим на то, что она знала отношения его к Наташе. – Она попросила, чтобы лучше познакомиться, позволить одной из барышень посидеть остальную часть спектакля в ее ложе, и Наташа перешла к ней.
В третьем акте был на сцене представлен дворец, в котором горело много свечей и повешены были картины, изображавшие рыцарей с бородками. В середине стояли, вероятно, царь и царица. Царь замахал правою рукою, и, видимо робея, дурно пропел что то, и сел на малиновый трон. Девица, бывшая сначала в белом, потом в голубом, теперь была одета в одной рубашке с распущенными волосами и стояла около трона. Она о чем то горестно пела, обращаясь к царице; но царь строго махнул рукой, и с боков вышли мужчины с голыми ногами и женщины с голыми ногами, и стали танцовать все вместе. Потом скрипки заиграли очень тонко и весело, одна из девиц с голыми толстыми ногами и худыми руками, отделившись от других, отошла за кулисы, поправила корсаж, вышла на середину и стала прыгать и скоро бить одной ногой о другую. Все в партере захлопали руками и закричали браво. Потом один мужчина стал в угол. В оркестре заиграли громче в цимбалы и трубы, и один этот мужчина с голыми ногами стал прыгать очень высоко и семенить ногами. (Мужчина этот был Duport, получавший 60 тысяч в год за это искусство.) Все в партере, в ложах и райке стали хлопать и кричать изо всех сил, и мужчина остановился и стал улыбаться и кланяться на все стороны. Потом танцовали еще другие, с голыми ногами, мужчины и женщины, потом опять один из царей закричал что то под музыку, и все стали петь. Но вдруг сделалась буря, в оркестре послышались хроматические гаммы и аккорды уменьшенной септимы, и все побежали и потащили опять одного из присутствующих за кулисы, и занавесь опустилась. Опять между зрителями поднялся страшный шум и треск, и все с восторженными лицами стали кричать: Дюпора! Дюпора! Дюпора! Наташа уже не находила этого странным. Она с удовольствием, радостно улыбаясь, смотрела вокруг себя.