Fluent Interfaces example using Delphi part I
I was introduced to Fluent interfaces when I was developing with Java using Dependency Injection and somebody told me that it could be really interesting if I post an example using Delphi and chaining method, and here I am!. I have developed a simple example that uses chaining methods to populate a THouse object with TDoors and TWindows objects with the hope it will make it easy to understand the concept. There are several examples on the net, but I couldn't find any interesting enough. So this simple example will try to introduce you to "how to develop fluent interfaces using method chaining" with the aim of writing more readable code. The overview of the architecture is the following one, using UML notation:
The Fluent Interface unit example is as follows (uFI.pas) using generics and interfaces:
// *********************************************** // @author jordi corbilla // @comment Fluent interface composition example // // *********************************************** unit uFI; interface uses Graphics, Contnrs, Generics.Collections, Classes; type TWindow = class; IWindow = interface function SetColor(color: TColor): TWindow; function ToString(): string; end; TWindow = class(TInterfacedObject, IWindow) private FColor: TColor; public function SetColor(color: TColor): TWindow; function ToString(): string; override; class function New: TWindow; end; TDoor = class; IDoor = interface function SetColor(color: TColor): TDoor; function ToString(): string; end; TDoor = class(TInterfacedObject, IDoor) private FColor: TColor; public function SetColor(color: TColor): TDoor; function ToString(): string; override; class function New: TDoor; end; IHouse = interface function AddWindow(window: TWindow): IHouse; function AddDoor(door: TDoor): IHouse; function ToString(): String; end; THouseItemsList<T> = class(TObjectList) function ToString(): string; override; end; TDoorList = class(THouseItemsList<IDoor>) function ToString(): string; override; public end; TWindowList = class(THouseItemsList<IWindow>) function ToString(): string; override; end; THouse = class(TInterfacedObject, IHouse) private FDoorList: TDoorList; FWindowList: TWindowList; protected function AddWindow(window: TWindow): IHouse; function AddDoor(door: TDoor): IHouse; public property WindowList: TWindowList read FWindowList; property DoorList: TDoorList read FDoorList; class function New: IHouse; function ToString(): String; override; constructor Create(); destructor Destroy(); override; end; implementation uses SysUtils; { THouse } function THouse.AddDoor(door: TDoor): IHouse; begin Self.FDoorList.Add(door); result := Self; end; function THouse.AddWindow(window: TWindow): IHouse; begin Self.FWindowList.Add(window); result := Self; end; constructor THouse.Create; begin FDoorList := TDoorList.Create; FWindowList := TWindowList.Create; end; destructor THouse.Destroy; begin FreeAndNil(FDoorList); FreeAndNil(FWindowList); inherited; end; class function THouse.New: IHouse; begin result := Create; end; function THouse.ToString: String; var description: TStringList; begin description := TStringList.Create; try description.Add(Self.FDoorList.ToString); description.Add(Self.FWindowList.ToString); finally result := description.Text; description.Free; end; end; { TWindow } class function TWindow.New: TWindow; begin result := Create; end; function TWindow.SetColor(color: TColor): TWindow; begin Self.FColor := color; result := Self; end; function TWindow.ToString: string; begin result := 'Window color ' + ColorToString(FColor) + sLineBreak; end; { TDoor } class function TDoor.New: TDoor; begin result := Create; end; function TDoor.SetColor(color: TColor): TDoor; begin Self.FColor := color; result := Self; end; function TDoor.ToString: string; begin result := 'Door color ' + ColorToString(FColor) + sLineBreak; end; { THouseItemsList } function THouseItemsList<T>.ToString: string; var i: Integer; description: String; begin for i := 0 to Self.count - 1 do description := description + ' ' + Self[i].ToString; result := description; end; { TDoorList } function TDoorList.ToString: string; var description: TStringList; begin description := TStringList.Create; try description.Add('Number of doors: ' + inttostr(Self.count)); description.Add('Descriptions: '); description.Add( inherited ToString); finally result := description.Text; FreeAndNil(description); end; end; { TWindowList } function TWindowList.ToString: string; var description: TStringList; begin description := TStringList.Create; try description.Add('Number of Windows: ' + inttostr(Self.count)); description.Add('Descriptions: '); description.Add( inherited ToString); finally result := description.Text; FreeAndNil(description); end; end; end.
Now it comes when we can use the object using FI notation in a more readable way:
var House: IHouse; begin House := THouse.New.AddWindow(TWindow.New.SetColor(clFuchsia)).AddDoor(TDoor.New.SetColor(clWhite)) .AddWindow(TWindow.New.SetColor(clRed)).AddDoor(TDoor.New.SetColor(clGreen)); Memo1.Lines.Add(House.ToString); House := nil; House := THouse.New; House.AddWindow(TWindow.New.SetColor(clRed)).AddWindow(TWindow.New.SetColor(clBlue)).AddWindow(TWindow.New.SetColor(clGreen)); House.AddDoor(TDoor.New.SetColor(clRed)).AddDoor(TDoor.New.SetColor(clBlue)).AddDoor(TDoor.New.SetColor(clGreen)); Memo1.Lines.Add(House.ToString); House := nil;
Notice that now, we can chain our objects, composing the structure of the House by using the AddWindow and AddDoor method. To display the result I'm using the ToString method that will do the job for my purpose.
And the result of the previous execution:
Number of doors: 2
Descriptions:
Door color clWhite
Door color clGreen
Number of Windows: 2
Descriptions:
Window color clFuchsia
Window color clRed
Number of doors: 3
Descriptions:
Door color clRed
Door color clBlue
Door color clGreen
Number of Windows: 3
Descriptions:
Window color clRed
Window color clBlue
Window color clGreen
Just play with it and you'll experience the powerfulness of this method. I hope you like it.
I'm looking forward to your comments.
Jordi
More examples of fluent interfaces in Delphi:
ReplyDeletehttp://www.nickhodges.com/post/Announcing-THTMLWriter.aspx,
http://www.thedelphigeek.com/search/label/XML
I like fluent interfaces at the right places but they are the killer when you try to debug something.
ReplyDeleteAlso this isn't the best example for showing the real benefit of fluent interfaces. Actually they are handy to use if you have some kind of grammar or you actually chain operations together and work with the result at the end (like chaining together some extension methods for IEnumerable in .Net for example)
Hi,
ReplyDeleteThanks for your comments. I will have a look at the provided examples. Even tough it isn't the best example, I'm sure will help to encourage and inspire others :)
Jordi
whats so readable in your example? :)
ReplyDeletethe code you provided looks like clumsy joke. or should i say perversion instead, huh?
the code similar to
with THouse.Create do
begin
with AddWindow do
begin
SetColor....
with AddDoor do
SetColor....
end;
end;
would look hundred times more readable.
PS. i always wondered about these endless tryings to invent new way of reproduction. don't you like initial one? ;)
Hi Mykhaylo,
ReplyDeleteI can see your point as well :)
I know it sounds like a joke, but sometimes when working with objects I like the idea of chaining components, just to change the way things are usually done.
I still believe that I find more readable the sentence Thouse.new.addWindow :), but it could become a nightmare if you try to do so with 100 of windows ;)
Anyway, Thanks for your comments.
Jordi
Jordi,
ReplyDeletebasically the code you've provided as example does not show any benefits of fluent interfaces.
first reason for that is that this code may be done the same way without using interfaces at all.
and the second reason is that such code is:
* hard to trace/debug
* hard to support/refactor
so this looks like mind game result rather than [possible] piece of commercial software.
just think of differences view when working with version control system. such view would be really useless for such code. and time lapse view will be even more useless.
for sure i can see some sense in making 'chains', but most likely there is very, very limited scope for that. the code should/may be arranged this way imho only if the source code structure itself is very consistent (i would say almost the same) with what this code implements. i don't know, maybe backus naur form definition or smth like that.
again, the debugging is going to be real pain in ass for such code
Hi Mykhaylo,
ReplyDeleteTaking into account that this is a simple example, I agree with you in terms of debugging the app. If we check out the wiki definition for fluent interface, it is stated:
"This style is marginally beneficial in readability due to its ability to provide a more fluid feel to the code. However, it can be detrimental to debugging, as a fluent chain constitutes a single statement for which debuggers may not allow setting up intermediate breakpoints, for instance."
This code could help others just to see the simple declaration to generate a chaining method and from here, try to improve the existing code with your own ideas.
Jordi
Pros and cons were discussed at StackOverflow a while ago:
ReplyDeletehttp://stackoverflow.com/q/5103877/267938
Could you maybe elaborate on your reasoning behind (sometimes) returning object references rather than interface references? In my experience, mixing the two is never a good idea, especially when the classes in question use the default reference counting implementation. After all, why bother with interfaces if you're accessing the objects directly anyway?
ReplyDeleteTwo important points:
ReplyDelete1. Turbo Vision used to have this "fluent" approach in the Turbo Pascal eon. It very much worked against you both when debugging, and readability as people tended to write overly complex streams.
2. At least until Delphi 2006, there was a compiler bug that when using fluent behaviour you would get a F2084 internal error (often DT5830, but I remember others).
As with all things in life: use an approach only when you have a clear benefit from it.
Hi Oliver,
ReplyDeleteI wanted to elaborate an example using interfaces and generics but unfortunately it wasn't good enough to demonstrate how to develop this using Delphi. I've used chaining method in little projects when working with different objects to build grammars or TStyle classes. In those scenarios it was easy to use the chaining method as it was quite intuitive to use:
TStyle.new.SetColorBrush(clBlack).setPenWidht(1)
or when using grammars to build different structures:
TFuncBuilder.new("X").add("+2") etc.
But the biggest challenge is the debugging, that it becomes extremely complicated.
Jordi
Jordi, I think you misunderstood me. I was talking about the difference between (for example):
ReplyDeletefunction SetColor(color: TColor): TWindow;
object ref ^
and:
function AddWindow(window: TWindow): IHouse;
intf ref ^
Why not stick to one or the other (personally, I'd always return interface references)?
Hi Oliver,
ReplyDeleteAs it is shown in the example it's a design choice.
I'm sure this would help:
Why interfaces are great"
This website attracts me to come back again and again to read the wonderful post,Nice one,Thanks for commenting,
ReplyDeleteWow, that is a great deal. I may have to see about fitting that into the plans for January.Interesting Strategy.i´ll try it on spanish market and
track if a get some results.Thanks for sharing.Reply
vps hosting reviews
good example and we can learn the inteface concept
ReplyDeleteThank you for your comment Srikanth!.
ReplyDelete