Friday, 26 August 2011

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

16 comments:

  1. More examples of fluent interfaces in Delphi:
    http://www.nickhodges.com/post/Announcing-THTMLWriter.aspx,
    http://www.thedelphigeek.com/search/label/XML

    ReplyDelete
  2. I like fluent interfaces at the right places but they are the killer when you try to debug something.

    Also 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)

    ReplyDelete
  3. Hi,

    Thanks 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

    ReplyDelete
  4. whats so readable in your example? :)
    the 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? ;)

    ReplyDelete
  5. Hi Mykhaylo,

    I 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

    ReplyDelete
  6. Jordi,

    basically 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

    ReplyDelete
  7. Hi Mykhaylo,

    Taking 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

    ReplyDelete
  8. Pros and cons were discussed at StackOverflow a while ago:
    http://stackoverflow.com/q/5103877/267938

    ReplyDelete
  9. 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?

    ReplyDelete
  10. Two important points:

    1. 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.

    ReplyDelete
  11. Hi Oliver,

    I 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

    ReplyDelete
  12. Jordi, I think you misunderstood me. I was talking about the difference between (for example):

    function 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)?

    ReplyDelete
  13. Hi Oliver,

    As it is shown in the example it's a design choice.

    I'm sure this would help:

    Why interfaces are great"

    ReplyDelete
  14. This website attracts me to come back again and again to read the wonderful post,Nice one,Thanks for commenting,
    Wow, 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

    ReplyDelete
  15. good example and we can learn the inteface concept

    ReplyDelete
  16. Thank you for your comment Srikanth!.

    ReplyDelete