Fluent interface
Текучий интерфейс (англ. fluent interface — в значении «плавный» или «гладкий» «интерфейс») в разработке программного обеспечения — способ реализацииобъектно-ориентированного API, нацеленный на повышениечитабельности исходного кода программы. Название придумано Эриком Эвансом и Мартином Фаулером.
Текучий интерфейс хорош тем, что упрощается множественный вызов методов одного объекта. Обычно это реализуется использованиемцепочки методов, передающих контекст вызова следующему звену (но текучий интерфейс влечет за собой нечто большее, чем просто цепочку методов[1]). Обычно, этот контекст:
- определён с помощью значения, возвращаемого методом;
- наследуется (в качестве нового контекста используется предыдущий);
- прекращается возвращением ничего не значащего значения (void).
Такой стиль косвенно полезен повышением наглядности и интуитивности кода[источник не указан 5416 дней]. Однако может весьма пагубно сказаться на отладке, если цепочка действует как одно выражение, куда отладчик не всегда может установить промежуточнуюточку останова.
Примеры
[править |править код]Delphi (Object Pascal)
[править |править код]Следующий пример показывает обычный класс и класс, реализующий текучий интерфейс, и различия в использовании. Пример написан на Delphi Object Pascal:
unitFluentInterface;interfacetypeIConfiguration=interfaceprocedureSetColor(Color:string);procedureSetHeight(height:integer);procedureSetLength(length:integer);procedureSetDepth(depth:integer);end;IConfigurationFluent=interfacefunctionSetColor(Color:string):IConfigurationFluent;functionSetHeight(height:integer):IConfigurationFluent;functionSetLength(length:integer):IConfigurationFluent;functionSetDepth(depth:integer):IConfigurationFluent;end;TConfiguration=class(TInterfacedObject,IConfiguration)privateFColor:string;FHeight:integer;FLength:integer;FDepth:integer;protectedprocedureSetColor(Color:string);procedureSetHeight(height:integer);procedureSetLength(length:integer);procedureSetDepth(depth:integer);end;TConfigurationFluent=class(TInterfacedObject,IConfigurationFluent)privateFColor:string;FHeight:integer;FLength:integer;FDepth:integer;protectedfunctionSetColor(Color:string):IConfigurationFluent;functionSetHeight(height:integer):IConfigurationFluent;functionSetLength(length:integer):IConfigurationFluent;functionSetDepth(depth:integer):IConfigurationFluent;publicclassfunctionNew:IConfigurationFluent;end;implementationprocedureTConfiguration.SetColor(Color:string);beginFColor:=Color;end;procedureTConfiguration.SetDepth(depth:integer);beginFDepth:=depth;end;procedureTConfiguration.SetHeight(height:integer);beginFHeight:=height;end;procedureTConfiguration.SetLength(length:integer);beginFLength:=length;end;classfunctionTConfigurationFluent.New:IConfigurationFluent;beginResult:=Create;end;functionTConfigurationFluent.SetColor(Color:string):IConfigurationFluent;beginFColor:=Color;Result:=Self;end;functionTConfigurationFluent.SetDepth(depth:integer):IConfigurationFluent;beginFDepth:=depth;Result:=Self;end;functionTConfigurationFluent.SetHeight(height:integer):IConfigurationFluent;beginFHeight:=height;Result:=Self;end;functionTConfigurationFluent.SetLength(length:integer):IConfigurationFluent;beginFLength:=length;Result:=Self;end;end.
varC,D:IConfiguration;E:IConfigurationFluent;begin{ Обычное использование:}C:=TConfiguration.Create;C.SetColor('blue');C.SetHeight(1);C.SetLength(2);C.SetDepth(3);{ обычная реализация, упрощенная с помощью инструкции with }D:=TConfiguration.Create;withDdobeginSetColor('blue');SetHeight(1);SetLength(2);SetDepth(3)end;{ использование реализации с текучим интерфейсом }E:=TConfigurationFluent.New.SetColor('Blue').SetHeight(1).SetLength(2).SetDepth(3);end;
C#
[править |править код]Начиная сC# 3.5 и выше введены продвинутые способы реализации текучего интерфейса:
namespaceExample.FluentInterfaces{#region Standard ExamplepublicinterfaceIConfiguration{stringColor{set;}intHeight{set;}intLength{set;}intDepth{set;}}publicclassConfiguration:IConfiguration{stringcolor;intheight;intlength;intdepth;publicstringColor{set{color=value;}}publicintHeight{set{height=value;}}publicintLength{set{length=value;}}publicintDepth{set{depth=value;}}}#endregion#region Fluent ExamplepublicinterfaceIConfigurationFluent{IConfigurationFluentSetColor(stringcolor);IConfigurationFluentSetHeight(intheight);IConfigurationFluentSetLength(intlength);IConfigurationFluentSetDepth(intdepth);}publicclassConfigurationFluent:IConfigurationFluent{stringcolor;intheight;intlength;intdepth;publicIConfigurationFluentSetColor(stringcolor){this.color=color;returnthis;}publicIConfigurationFluentSetHeight(intheight){this.height=height;returnthis;}publicIConfigurationFluentSetLength(intlength){this.length=length;returnthis;}publicIConfigurationFluentSetDepth(intdepth){this.depth=depth;returnthis;}}#endregionpublicclassExampleProgram{publicstaticvoidMain(string[]args){// Обычный примерIConfigurationconfig=newConfiguration{Color="blue",Height=1,Length=2,Depth=3};// Пример текучего интерфейсаIConfigurationFluentfluentConfig=newConfigurationFluent().SetColor("blue").SetHeight(1).SetLength(2).SetDepth(3);}}}
C++
[править |править код]Банальный пример вC++ — стандартныйiostream, где текучесть обеспечиваетсяперегрузкой операторов.
Примеробертки текучего интерфейса в C++:
// обычное заданиеclassGlutApp{private:intw_,h_,x_,y_,argc_,display_mode_;char**argv_;char*title_;public:GlutApp(intargc,char**argv){argc_=argc;argv_=argv;}voidsetDisplayMode(intmode){display_mode_=mode;}intgetDisplayMode(){returndisplay_mode_;}voidsetWindowSize(intw,inth){w_=w;h_=h;}voidsetWindowPosition(intx,inty){x_=x;y_=y;}voidsetTitle(constchar*title){title_=title;}voidcreate();};// обычное использованиеintmain(intargc,char**argv){GlutAppapp(argc,argv);app.setDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_ALPHA|GLUT_DEPTH);// Set framebuffer paramsapp.setWindowSize(500,500);// Set window paramsapp.setWindowPosition(200,200);app.setTitle("My OpenGL/GLUT App");app.create();}// Обертка текучего интерфейсаclassFluentGlutApp:privateGlutApp{public:FluentGlutApp(intargc,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(intw,inth){setWindowSize(w,h);return*this;}FluentGlutApp&at(intx,inty){setWindowPosition(x,y);return*this;}FluentGlutApp&named(constchar*title){setTitle(title);return*this;}// без разницы, вести ли цепь после вызова create(), поэтому не возвращаем *thisvoidcreate(){GlutApp::create();}};// используем текучий интерфейсintmain(intargc,char**argv){FluentGlutAppapp(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:
publicCollection<Student>findByNameAgeGender(Stringname,intage,Gendergender){returnem.createNamedQuery("Student.findByNameAgeGender").setParameter("name",name).setParameter("age",age).setParameter("gender",gender).setFirstResult(1).setMaxResults(30).setHint("hintName","hintValue").getResultList();}
Библиотекаop4j позволяет использовать текучий интерфейс для выполнения вспомогательных задач, вродеитерированияструктур, конвертирования информации, фильтрации, и т. д.
String[]datesStr=newString[]{"12-10-1492","06-12-1978"};...List<Calendar>dates=Op.on(datesStr).toList().map(FnString.toCalendar("dd-MM-yyyy")).get();
Также, библиотекаMock-объект тестированияEasyMock активно использует этот стиль для предоставления удобного интерфейса.
CollectionmockCollection=EasyMock.createMock(Collection.class);EasyMock.expect(mockCollection.remove(null)).andThrow(newNullPointerException()).atLeastOnce();
PHP
[править |править код]Пример реализации класса с текучим интерфейсом вPHP:
classCar{private$speed,$color,$doors;publicfunctionsetSpeed($speed){$this->speed=$speed;return$this;}publicfunctionsetColor($color){$this->color=$color;return$this;}publicfunctionsetDoors($doors){$this->doors=$doors;return$this;}}// Обычная реализация$myCar2=newCar();$myCar2->setSpeed(100);$myCar2->setColor('blue');$myCar2->setDoors(5);// Текучий интерфейс$myCar=newCar()->setSpeed(100)->setColor('blue')->setDoors(5);
JavaScript
[править |править код]Пример реализации класса с текучим интерфейсом вJavaScript:
varCar=(function(){varspeed,color,doors,pub;functionsetSpeed(new_speed){speed=new_speed;returnpub;}functionsetColor(new_color){color=new_color;returnpub;}functionsetDoors(new_doors){doors=new_doors;returnpub;}pub={'setSpeed':setSpeed,'setColor':setColor,'setDoors':setDoors,};returnpub;})// Обычная реализация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.$){returnnew$(selector);}if(typeofselector=="string"){this.init=document.getElementById(selector);}};$.prototype={text:function(text){if(!text){this.init.innerHTML;}this.init.innerHTML=text;returnthis;},css:function(style){for(variinstyle){this.init.style[i]=style[i];}returnthis;}};//пример использования:$('div').text('div').css({color:"red"});
Пример независящей от типа возвращаемого объекта реализации:
({foo:function(a){returna;}}).foo('foo').toUpperCase();
Примечания
[править |править код]- ↑MF Bliki: FluentInterface . Дата обращения: 26 октября 2010. Архивировано 8 марта 2021 года.
Ссылки
[править |править код]- Страницы, использующие устаревший тег source
- Википедия:Cite web (не указан язык)
- Википедия:Статьи без источников (тип: не указан)
- Википедия:Нет источников с апреля 2011
- Википедия:Статьи с утверждениями без источников более 14 дней
- Статьи с примерами кода Object Pascal
- Статьи с примерами кода C Sharp
- Статьи с примерами кода C++
- Статьи с примерами кода Java
- Статьи с примерами кода PHP
- Статьи с примерами кода JavaScript