4

I have following fluent interface declaration and class that implements that interface:

type
  IDocWriter = interface
    ['{8CB5799A-14B1-4287-92FD-41561B237560}']
    function Open: IDocWriter;
    function Close: IDocWriter;
    function Add(const s: string): IDocWriter;
    function SaveToStream(Stream: TStream): IDocWriter;
  end;

  TDocWriter = class(TInterfacedObject, IDocWriter)
  public
    function Open: IDocWriter;
    function Close: IDocWriter;
    function Add(const s: string): IDocWriter;
    function SaveToStream(Stream: TStream): IDocWriter;
  end;

{ TDocWriter }

function TDocWriter.Open: IDocWriter;
begin
  Result := Self;
  // DoOpen
end;

function TDocWriter.Close: IDocWriter;
begin
  Result := Self;
  // DoClose
end;

function TDocWriter.Add(const s: string): IDocWriter;
begin
  Result := Self;
  // DoAdd
end;

function TDocWriter.SaveToStream(Stream: TStream): IDocWriter;
begin
  Result := Self;
  // DoSaveToStream
end;

And I can use above code like this:

var
  Stream: TStream;
  ...
  TDocWriter.Create
    .Open
    .Add('abc')
    .Close
    .SaveToStream(Stream);

I have to extend above interface by adding SaveToString function.

I don't want to add that method to original IDocWriter interface because it is not valid method for all interface implementations. So I have done following

type
  IStrDocWriter = interface(IDocWriter)
    ['{177A0D1A-156A-4606-B594-E6D20818CE51}']
    function SaveToString: string;
  end;

  TStrDocWriter = class(TDocWriter, IStrDocWriter)
  public
    function SaveToString: string;
  end;

{ TStrDocWriter }

function TStrDocWriter.SaveToString: string;
begin
  Result := 'DoSaveToString';
end;

In order to use IStrDocWriter interface I have to write code

var
  Writer: IDocWriter;
  s: string;

  Writer := TStrDocWriter.Create
    .Open
    .Add('abc')
    .Close;
  s := (Writer as IStrDocWriter).SaveToString;

But I would like to be able to use it without the need to declare Writer variable, something like following code (which, of course, cannot be compiled)

  s := TStrDocWriter.Create
    .Open
    .Add('abc')
    .Close
    .SaveToString;   // Undeclared identifier SaveToString

Is there any way to achieve that?

Any kind of changes to above interfaces and classes are acceptable (except, obviously, merging those two interfaces into one).

mjn
  • 36,362
  • 28
  • 176
  • 378
Dalija Prasnikar
  • 27,212
  • 44
  • 82
  • 159
  • 1
    The entire design looks wrong to me. Fluent is just so bad! – David Heffernan Jan 24 '15 at 12:47
  • Pardon my curiousity, but is this interface v. fluent requirement self-inflicted, or does it arise from an actual business requirement and, if so, how? – MartynA Jan 24 '15 at 22:47
  • 1
    @MartynA it is self-inflicted with reason. Interfaces because it is ref-counted and removes need for try..finally blocks. Fluent because it removes the need for declaring variables and makes code more readable when you have longish chain. It can be used inline in otherwise messy code that you can usually have while creating some document or report. Imagine TTextDocWriter, TRichTextDocWriter, TPdfDocWriter, TMetafileDocWriter... – Dalija Prasnikar Jan 25 '15 at 10:08

1 Answers1

4

You can write it like this:

s := (TStrDocWriter.Create
    .Open
    .Add('abc')
    .Close as IStrDocWriter)
    .SaveToString; 

No, not very nice is it. These two idioms, fluent interfaces and inheritance, simply do not mix.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490