14.3.2 特定类约束
如果您的泛型类需要使用某个特定子集的类(特定层次结构),则可能需要根据给定基类指定约束。
例如,如果您声明:
type
TCompClass<T: TComponent> = class
则此泛型类的实例仅适用于组件类,即任何TComponent
后代类。这使您拥有一个非常特定的泛型类型(是的,听起来很奇怪,但这确实是它的实际情况),并且编译器将允许您在处理泛型类型时使用TComponent
类的所有方法。
如果这看起来非常强大,那么请三思。如果你考虑一下利用继承和类型兼容规则可以实现的功能,也许你可以使用传统的面向对象技术来解决同样的问题,而不必使用泛型类。我并不是说特定的类约束从来都没有用,但它肯定没有更高级别的类约束或(我觉得非常有趣的)基于接口的约束强大。
14.3.3 接口约束
一般来说,更灵活的做法是只接受实现特定接口的类作为类型参数,而不是将一个泛型类约束为一个给定的类。这样就可以在泛型的实例上调用接口。在 C# 语言中,将接口约束用于泛型也很常见。让我先展示给你一个示例(来自IntfConstraint
示例)。首先,我们需要声明一个接口:
type
IGetValue = interface
['{60700EC4-2CDA-4CD1-A1A2-07973D9D2444}']
function GetValue: Integer;
procedure SetValue(Value: Integer);
property Value: Integer read GetValue write SetValue;
end;
接下来,我们可以定义一个实现它的类:
type
TGetValue = class(TNoRefCountObject, IGetValue)
private
FValue: Integer;
public
constructor Create(Value: Integer = 0);
function GetValue: Integer;
procedure SetValue(Value: Integer);
end;
在限制为实现特定接口的类型的泛型类的定义中,事情开始变得有趣:
type
TInftClass<T: IGetValue> = class
private
FVal1, FVal2: T; // Or IGetValue
public
procedure Set1(Val: T);
procedure Set2(Val: T);
function GetMin: Integer;
function GetAverage: Integer;
procedure IncreaseByTen;
end;
请注意,在这个类的泛型方法的代码中,我们可以编写:
function TInftClass<T>.GetMin: Integer;
begin
Result := Min(FVal1.GetValue, FVal2.GetValue);
end;
procedure TInftClass<T>.IncreaseByTen;
begin
FVal1.SetValue(FVal1.GetValue + 10);
FVal2.Value := FVal2.Value + 10;
end;
有了所有这些定义,我们现在可以按以下方式使用泛型类:
procedure TFormIntfConstraint.BtnValueClick(Sender: TObject);
var
IClass: TInftClass<TGetValue>;
begin
IClass := TInftClass<TGetValue>.Create;
try
IClass.Set1(TGetValue.Create(5));
IClass.Set2(TGetValue.Create(25));
Show('Average: ' + IntToStr(IClass.GetAverage));
IClass.IncreaseByTen;
Show('Min: ' + IntToStr(IClass.GetMin));
finally
IClass.FVal1.Free;
IClass.FVal2.Free;
IClass.Free;
end;
end;
为了展示这个泛型类的灵活性,我为接口创建了另一个完全不同的实现方法:
type
TButtonValue = class(TButton, IGetValue)
public
function GetValue: Integer;
procedure SetValue(Value: Integer);
class function MakeTButtonValue(Owner: TComponent; Parent: TWinControl): TButtonValue;
end;
function TButtonValue.GetValue: Integer;
begin
Result := Left; // use base class property
end;
procedure TButtonValue.SetValue(Value: Integer);
begin
Left := Value; // use base class property
end;
该类函数(此处未显示)在父控件中创建了一个随机位置的按钮。位置创建一个按钮,并在以下示例代码中使用:
procedure TFormIntfConstraint.BtnValueButtonClick(Sender: TObject);
var
IClass: TInftClass<TButtonValue>;
begin
IClass := TInftClass<TButtonValue>.Create;
try
IClass.Set1(TButtonValue.MakeTButtonValue(Self, ScrollBox1));
IClass.Set2(TButtonValue.MakeTButtonValue(Self, ScrollBox1));
Show('Average: ' + IntToStr(IClass.GetAverage));
Show('Min: ' + IntToStr(IClass.GetMin));
IClass.IncreaseByTen;
Show('New Average: ' + IntToStr(IClass.GetAverage));
finally
IClass.Free;
end;
end;