开发者

Is Fluent call style self referencing records in Delphi possible?

开发者 https://www.devze.com 2023-03-25 14:31 出处:网络
The goal is to create a type called TURLString to be called as follows: var newURl : TURLString; begin newURL.Append(\'http://\').Append开发者_开发百科(\'www.thehost.com\').Append(\'path/on/server\'

The goal is to create a type called TURLString to be called as follows:

var
  newURl : TURLString;
begin

  newURL.Append('http://').Append开发者_开发百科('www.thehost.com').Append('path/on/server').Append('?');

  ...lots of app logic...

  newURL.AppendParam('name', 'value').Append('#').AppendParam('name', 'value');

  ...more params added...

  result := httpClient.Get(newURL);

end;

With TURLString defined like this (note its a record):

//from actual code used
TURLString = record
private
    FString : string;
public
    function Append(APart : string) : TURLString;
    function AppendParam(AParam, AValue : string) : TURLString;
end;

function TURLString.Append(APart: string) : TURLString;
begin
  FString := FString + APart;
  result := self;
end;

function TURLString.AppendParam(AParam, AValue: string): TURLString;
begin
  if (not Empty) then
    FString := FString + URL_AMB;
  FString := FString + AParam + '=' + AValue;
  result := self;
end;

When stepping through the fluid calls, the values are appended but when exiting they revert to the first string passed into the first append call and newURL is equal to 'http://' while debugging the append call you see 'http://www.thehost.com/path/on/server?name=value#name=value'.

Is this concept possible with a record?


To get rid of the performance and memory use problem of record copy, you may use pointers as results type for your methods:

type
  PURLString = ^TURLString;
  TURLString = record
  private
    FString : string;
  public
    function Append(const APart : string) : PURLString;
    function AppendParam(const AParam, AValue : string) : PURLString;
  end;

function TURLString.Append(const APart: string) : PURLString;
begin
  FString := FString + APart;
  result := @self;
end;

function TURLString.AppendParam(const AParam, AValue: string): PURLString;
begin
  if FString <> '' then
    FString := FString + URL_AMB;
  FString := FString + AParam + '=' + AValue;
  result := @self;
end;

So you may be able to call your original TURLString just as wished:

newURL.Append('http://').Append('www.thehost.com').Append(...

And don't forget the const keyword for string parameters of methods.

Note that the proper way of using a pointer should have been

newURL.Append('http://')^.Append('www.thehost.com')^.Append(...

but in fact the Delphi compiler is clever enough to add the ^ sign implicitly.

With this trick, you don't have to create nor free your newURL instance. Note that only reference-counted parameters (string, variants, interfaces) will be initialized on the stack: integers or doubles will be pure random.

This is exactly what I used for creating some RTF content in my SynProject documentation tool (at least in the first versions, now I use a class instead of a record, since I wanted to add inheritance).


The record style creates a new, anonymous (and therefore unreachable, for you) record for each call and the Result = self; line copies the "current" record to the new one. This may not be what you want. As David says, you'll have to assign the (anonymous) end result to a record variable you declared, so you can finally access the end result.

If you use a reference type (object or interface), it will return a reference to itself, and no new object is generated (and nothing is copied). That makes a lot more sense in the fluid style.


If you use a value type like a record then you need to assign the final returned result to a variable:

newURL := newURL.Append('http://').Append('www.thehost.com');

If you use a reference type like a class instance, then you can use the syntax that you used in your question.

The reference type approach treats the data type as mutable, whereas for value types you are best implementing an immutable data type.


Just take a look at my TPathBuilder implementation here. It's main purpose is to build file paths but you can get the idea. Source code is included in the post.

0

精彩评论

暂无评论...
验证码 换一张
取 消