7.4 Self 标识符
我们已经看到类的方法非常类似于过程和函数。真正的区别在于,方法有一个额外的隐式参数。这是对当前对象的引用,即应用方法的对象。在方法中,您可以使用Self
标识符引用这个参数,即当前对象,如前面第5章“Self:记录背后的魔法”部分所述。
当您创建同一类的多个对象时,这个额外的隐藏参数是必需的,以便每次将方法应用于其中一个对象时,该方法将仅操作该特定对象的数据,而不影响同一类的其他对象。
注解:Self标识符的概念和实现在记录和类之间非常相似。从历史上讲,
Self
首先用于类,后来在向记录添加方法时也扩展到了记录。
例如,在先前列出的TDate
类的SetValue
方法中,我们简单地使用Month、Year和Day引用当前对象的字段,您可能会表示为:
Self.FMonth := M;
Self.FDay := D;
实际上,这是Object Pascal编译器翻译代码的方式,而不是您应该编写代码的方式。Self
标识符是编译器使用的基本语言构件,但有时程序员使用它来解决名称冲突并使代码更具可读性。
注解:C++、Java、C#和JavaScript语言都具有类似于this关键字的功能。但是,在JavaScript中,使用this在方法中引用对象字段是强制性的,这点不同于C++、C#和Java。
关于Self,您真正需要知道的是调用方法的技术实现与调用通用子例程的实现有所不同。方法有一个额外的隐藏参数Self。由于所有这些都在幕后进行,您现在不需要知道Self是如何工作的。
第二个重要的事情是您可以显式使用Self引用整个当前对象,例如将当前对象作为参数传递给另一个函数。
7.4.1 动态创建组件
举个刚才提到的例子,当你需要在某个方法中明确引用当前窗体时,通常会用到 Self 标识符。
一个典型的例子是在运行时创建一个组件,在这种情况下,你必须将组件的所有者传给它的 Create 构造函数,并将相同的值赋给它的 Parent 属性。在这两种情况下,你都必须提供当前窗体对象作为参数或值,而最好的方法就是使用 Self 标识符。
注解:组件的所有权表示两个对象之间的生命周期和内存管理关系。当组件的所有者被释放时,组件也将被释放。父子关系指的是可视化控件托管在其表面一个子控件。
为了演示这类代码,我编写了 CreateComps 示例。这个应用程序有一个没有组件的简单窗体,它有一个 OnMouseDown 事件的处理程序,该处理程序的参数还包括鼠标点击的位置。我需要这些信息来在该位置创建一个按钮组件。
注解:事件处理程序是第 10 章中涉及的一种特殊方法,与我们在本书中已经使用过的按钮 OnClick 事件处理程序同属一个系列。
这是该方法的代码:
procedure TForm1.FormMouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
Btn: TButton;
begin
Btn := TButton.Create(Self);
Btn.Parent := Self;
Btn.Position.X := X;
Btn.Position.Y := Y;
Btn.Height := 35;
Btn.Width := 135;
Btn.Text := Format('At %d, %d', [X, Y]);
end;
请注意,您可能需要将StdCtrls
单元添加到uses语句以编译此事件处理程序。
如图 7.2 所示,这段代码的作用是在鼠标点击的位置创建按钮,并用标题标明具体位置(在本项目中,我禁用了 FMX 移动预览,以显示本地风格的 Windows 按钮,因为这样更清晰)。在上面的代码中,请特别注意 Self 标识符的使用,它既是 Create 方法的参数,也是 Parent 属性的值。
在编写类似刚才代码的过程时,你可能会倾向于使用 Form1 变量而不是 Self。在这个具体的例子中,这种改变不会带来任何实际的差别(尽管这不是一个好的编码实践),但如果一个窗体有多个实例,使用 Form1 就真的是一个错误了。
实际上,如果 Form1 变量指的是用该类型创建的窗体(一般是第一个窗体),并且如果创建了两个相同窗体类型的实例,通过点击任何后续窗体,新按钮将始终显示在第一个窗体中。它的所有者和父窗体将是Form1,而不是用户点击过的窗体。
一般来说,在编写方法时,如果需要当前对象,就会引用同一类中的特定实例,这确实是一种糟糕的 OOP 编码风格。
图7.2: CreateComps示例在移动设备上的输出