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
















