Delphi 中的“with command”和“fluent interface”有什么区别?

siv3szwd  于 2023-08-04  发布在  其他
关注(0)|答案(2)|浏览(111)

为什么我应该在 Delphi 中使用流畅的界面而不是使用“with command”?
我都听说过,但我还没发现区别。我正在为我的项目寻找最好的一个。它似乎以相同的方式工作,只是有一些语法差异。

pb3skfrl

pb3skfrl1#

有一些(非常,非常少)用例中,with使用起来是“安全的”,但应该避免使用。
因此,我不打算(详细)详细讨论为什么你应该或不应该使用with,并坚持回答你问题的第二部分:显然误解了with和fluent API在某种程度上是直接相关的,而实际上它们并不相关。

with

with接受一个或多个符号并创建一个作用域,其中这些符号优先于包含作用域中同名的其他符号:
因此,如果我们想象我们正在某个表单上编写代码,并且需要在该表单上设置某个组件的多个属性,其中一些属性也存在(即,使用相同的名称),我们可以使用with创建一个作用域,在该作用域中,对那些属性的非限定引用解析为组件,而不是表单:

procedure frmMain.ButtonClick(Sender TComponent);
begin 
   with foo do
   begin
      Caption := 'click me!';
      Tag     := 42;
   end;
end;

字符串
另一种方法是不使用with,而是使用对foo属性的限定引用:

procedure frmMain.ButtonClick(Sender TComponent);
begin 
   foo.Caption := 'click me!';
   foo.Tag     := 42;
end;


这显然不是一个“流畅”的API。那是什么

Fluent API

流畅API是一个术语,用于描述一种模式,其中对象的方法返回对该对象的引用,允许通过链接它们来调用其他方法。
坚持假设的foo组件,如果我们想象该组件的开发人员提供了一个流畅的API来设置属性,而不是(或者也可能)直接将它们公开为属性,那么我们可以想象可能编写类似于下面的代码:

procedure frmMain.ButtonClick(Sender TComponent);
begin 
   foo.SetCaption('click me!')
      .SetTag(42);
end;


SetCaption()SetTag()返回调用它们的对象,允许对该对象的进一步调用链接在一起。
表面上看起来与with模式相似,虽然不那么“罗嗦”,但有两个关键区别:

  1. Fluent API调用是单个语句; with方法涉及 * 多个 * 语句
    1.流畅的API调用是对 * 显式地 * 标识的对象进行的;使用with进行的调用 * 隐式 * 标识目标对象
    with和fluent API的混淆可能源于with * 与 * a fluent api * 结合使用 * 本质上只是语法的变体:
procedure frmMain.ButtonClick(Sender TComponent);
begin 
   with foo do
   begin
     SetCaption('click me!')
     SetTag(42);
   end
end;


但关键分歧依然存在。上面的代码是 two statements implicitly using foo vs the single statement explicitly calling foo in the fluent variant.
一个显著的额外区别是with可以与 * 多个符号 * 一起使用(如果你绝对决心让你和你的同事的生活变得困难:))。这有(希望)明显的潜在可能性,使得很难(一眼)分辨with作用域中的哪些引用解析为哪些符号。
另一方面,流畅的API总是从某个对象开始,链总是从那个对象继续。对于你正在处理的符号,从来没有任何疑问或怀疑。
只是为了好玩,我们可以通过观察with和流畅度是正交的,看看这两者如何一起使用来创造一个真实的的怪物:

procedure frmMain.ButtonClick(Sender TComponent);
begin 
   with foo do
   begin
     SetCaption('click me!')
     .SetTag(42);
   end;
end;

那么相似性在哪里呢?

唯一真实的的相似之处是,流畅API提供了几乎相同的好处,即不必限定每个调用(或者更准确地说,* 通过使每个后续调用的限定成为前一个调用的一部分 *)。
with的一个大问题是,迄今为止的调试器和IDE工具仍然无法像编译器那样解析符号,这可能会导致在尝试调试with语句中的代码时出现重大问题。在调试器中检查with作用域值可能会解析到错误的符号,从而给出错误的值。
Fluent API有自己的调试挑战,最明显的挑战是Fluent API调用的链式序列是一个语句,在该语句上只能放置一个断点。

应该用哪一种?

作为API的消费者...

如果你面对的是一个没有流畅API的场景,那么在with和流畅api之间别无选择。仅决定是否使用with。这是一个长期而激烈的辩论。
对此的简短回答是(IMHO):* 不要使用with,除非/直到你确信地确定了(非常,非常少数)边缘情况,这样做是安全和有益的 *。同时,默认为“不”。
如果有一个流利的API,那就使用它。它提供了非常相似的语法速记,没有with的缺陷,尽管在断点方面有一些粒度的缺失,正如前面提到的。
当然,第三种选择是既不使用with也不使用fluency,而只是引用fluent API链的“根”,并在单独的语句中使用它,而不是链接调用。

作为API的开发者

想想你的代码将如何被使用,并考虑一个流畅的API是否有意义。“Builder”类型的API非常常见地使用Fluent API,也有其他用例,但有些Fluent API非常不直观,使用起来可能会很麻烦;它们不是万灵药。
如果你在API中遇到了一个边缘情况,不可能使用流畅性,那么你就有可能让你的用户处于一种不舒服的境地,在某些方面使用流畅性,而在其他方面却无法使用,这可能会让你非常沮丧,因为你必须记住api的哪些部分是以何种方式工作的。
希望能帮上忙。

sirbozc5

sirbozc52#

上面的解释很好,但从你的问题中看不出你在寻找什么。这里有一个稍微不同的看法。
假设你有一个类TFoo,它有几个不同的方法。在你的代码中,你可能有机会写这样的东西:

foo.turnOn;
foo.doThis;
foo.Caption := 'a caption';
foo.Update( aRecPtr );
foo.turnOff;

字符串
使用“with”可以很容易地做到:

with foo do
begin
  turnOn;
  doThis;
  Caption := 'a caption';
  Update( aRecPtr );
  turnOff;
end;


在这种情况下,使用“流畅”方法不会直接起作用,因为这些方法都不可能返回对foo的引用。(你可能会注意到没有一个VCL方法是为此而设计的。)所以你需要重写你需要的那些方法(或者整个类),这样你就可以做下面的事情:

foo.turnOn
   .doThis
   .Caption( 'a caption' )
   .Update( aRecPtr )
   .turnOff;


这并不是很难,你可以为任何类型的'foo'创建一个Helper类。每一个都需要是一个返回基类型的函数:

type
TFooHelper = class helper for TFoo
  function turnOn : TFoo;
  function Caption( const str : string ) : TFoo;
end;

function TFooHelper.turnOn : TFoo;
begin
  self.turnOn;  // Self refers to TFoo type, not TFooHelper
  Result := self;
end;

function TFooHelper.Caption( const str : string ) : TFoo;
begin
  self.Caption := str;
  Result := self;
end;


等等。

相关问题