Classes e helpers
Classes, visibilidade, métodos virtuais, properties, herança com inherited e os helpers que acrescentam métodos a tipos existentes.
Declarando uma classe#
program Classes;
type
TAnimal = class(TObject)
private
FName: UString;
public
constructor Create(const AName: UString);
procedure Speak; virtual; abstract;
function Describe: UString; virtual;
property Name: UString read FName write FName;
end class;
TDog = class(TAnimal)
public
procedure Speak; override;
end class;
TCat = class(TAnimal)
public
procedure Speak; override;
function Describe: UString; override;
end class;
constructor TAnimal.Create(const AName: UString);
begin
FName := AName;
end constructor;
function TAnimal.Describe: UString;
begin
Exit('animal ' + FName);
end function;
procedure TDog.Speak;
begin
WriteLn(Name, ': au au');
end procedure;
procedure TCat.Speak;
begin
WriteLn(Name, ': miau');
end procedure;
function TCat.Describe: UString;
begin
Exit('gato ' + inherited Describe);
end function;
var
Pets: array[0..1] of TAnimal;
Pet: TAnimal;
begin
Pets[0] := TDog.Create('Rex');
Pets[1] := TCat.Create('Mimi');
for Pet in Pets do
begin
Pet.Speak; // despacho virtual
WriteLn(' ', Pet.Describe);
if Pet is TCat then
WriteLn(' (é um gato)');
end for;
end.Rex: au au
animal Rex
Mimi: miau
gato animal Mimi
(é um gato)type
TAnimal = class(TObject)
private
FName: UString;
public
constructor Create(const AName: UString);
procedure Speak; virtual; abstract;
property Name: UString read FName write FName;
end class;O fecho pode ser end; ou o qualificado end class;. O ancestral é opcional (class sozinho herda de TObject), e uma classe pode implementar interfaces na mesma lista: class(TObject, IShape).
Visibilidade#
private, protected, public, published e strict (com private e protected) valem dentro da classe. O corpo de um método não leva visibilidade: quem manda é a declaração da classe.
Métodos virtuais#
| Palavra | Efeito |
|---|---|
virtual | o método pode ser sobrescrito; o despacho é pela VMT |
override | sobrescreve um virtual do ancestral |
abstract | sem corpo; os descendentes devem sobrescrever |
reintroduce | esconde de propósito um virtual de mesmo nome |
final | num método: ninguém sobrescreve; numa classe: final class, a árvore para nela |
overload | o conjunto do descendente se junta ao do ancestral |
static | método sem Self (static function X) |
Para o otimizador, duas marcas ajudam muito:
type
TSale = class(TObject)
public
FCustomer: exact TCustomer; // guarda EXATAMENTE TCustomer, nunca um descendente
end class;
TSupplier = final class(TObject); // a árvore para aqui
TBase = class abstract(TObject); // classe abstrata: só um descendente concreto é instanciadoexact T deixa o compilador resolver a chamada virtual em compilação (devirtualização), e a devirtualização de métodos sem override já é feita sozinha. Ela pode ser desligada por {$config devirtualization off}.
Construtores, destrutores e class constructor#
constructor Create(...) e destructor Destroy; override; funcionam como no Delphi. Um class constructor roda uma vez, no arranque, e é podado se o tipo morrer na eliminação de código não usado. inherited chama o ancestral.
inherited, em três degraus#
| Forma | Chega em |
|---|---|
inherited; | o mesmo método, um degrau acima |
inherited Nome(…) | outro método, um degrau acima |
inherited TClasseA.Nome | um degrau escolhido da cadeia |
A terceira forma existe porque uma cadeia de override só deixa chegar ao pai imediato, e às vezes o que se quer é o de mais longe. A classe citada tem de estar na cadeia de quem escreve.
is, as e metaclasses#
Obj is TCat testa o tipo; Obj as TCat converte com verificação. class of T é uma metaclasse: um tipo cujos valores são classes (TClass é class of TObject) e que aceita constantes tipadas.
Properties#
property Celsius: Float64 read FCelsius write FCelsius; // por campo
property Fahrenheit: Float64 read GetFahrenheit write SetFahrenheit; // por método
property Reading[const AIndex: Int32]: Int32 read GetReading write SetReading;program Properties;
type
TTemperature = class(TObject)
private
FCelsius: Float64;
FHistory: array[0..3] of Int32;
function GetFahrenheit: Float64;
procedure SetFahrenheit(const AValue: Float64);
function GetReading(const AIndex: Int32): Int32;
procedure SetReading(const AIndex: Int32; const AValue: Int32);
public
// leitura e escrita por campo
property Celsius: Float64 read FCelsius write FCelsius;
// leitura e escrita por método
property Fahrenheit: Float64 read GetFahrenheit write SetFahrenheit;
// property indexada: os índices vão na frente do valor
property Reading[const AIndex: Int32]: Int32 read GetReading write SetReading;
end class;
function TTemperature.GetFahrenheit: Float64;
begin
Exit(FCelsius * 9 / 5 + 32);
end function;
procedure TTemperature.SetFahrenheit(const AValue: Float64);
begin
FCelsius := (AValue - 32) * 5 / 9;
end procedure;
function TTemperature.GetReading(const AIndex: Int32): Int32;
begin
Exit(FHistory[AIndex]);
end function;
procedure TTemperature.SetReading(const AIndex: Int32; const AValue: Int32);
begin
FHistory[AIndex] := AValue;
end procedure;
var
T: TTemperature;
begin
T := TTemperature.Create;
T.Celsius := 100;
WriteLn(T.Fahrenheit);
T.Fahrenheit := 32;
WriteLn(T.Celsius);
T.Reading[2] := 99;
WriteLn(T.Reading[2]);
end.212
0
99- Numa property indexada, os índices são parâmetros de verdade e vão para o acessor na frente do valor: o getter recebe só os índices, o setter recebe os índices e o valor por último. Ligar uma property indexada direto a um campo não existe.
property Name: UString DefReadWrite;(sem os acessores) sintetiza os acessores sobre o campoF<Nome>; há tambémDefReadeDefWrite. A visibilidade dos sintetizados é ajustável ({$config DefGetVisibility private},DefField, …).- Uma property de interface é açúcar puro sobre os métodos dela.
Helpers#
Um helper for acrescenta métodos a um tipo que já existe — uma classe, um record ou até um tipo básico:
program Helpers;
type
// acrescenta métodos a um tipo que já existe (aqui, um tipo básico)
TInt32Helper = helper for Int32
public
function Double: Int32;
function IsEven: Bool8;
static function FromText(const S: UString): Int32;
end helper;
TGreeter = class(TObject)
public
procedure Hello(const S: UString);
end class;
// o helper GANHA do tipo quando os dois declaram o mesmo nome; o escondido
// continua alcançável por inherited
TGreeterHelper = helper for TGreeter
public
procedure Hello(const S: UString);
end helper;
function TInt32Helper.Double: Int32;
begin
Exit(Self * 2); // Self é o próprio Int32
end function;
function TInt32Helper.IsEven: Bool8;
begin
Exit(Self mod 2 = 0);
end function;
static function TInt32Helper.FromText(const S: UString): Int32;
begin
Exit(UStrToInt32(S));
end function;
procedure TGreeter.Hello(const S: UString);
begin
WriteLn('tipo: ', S);
end procedure;
procedure TGreeterHelper.Hello(const S: UString);
begin
WriteLn('helper: ', S);
inherited Hello(S); // chega no método do tipo estendido
end procedure;
var
N: Int32;
G: TGreeter;
begin
N := 21;
WriteLn(N.Double, ' ', N.IsEven, ' ', TInt32Helper.FromText('40'));
G := TGreeter.Create;
G.Hello('olá');
end.42 FALSE 40
helper: olá
tipo: oláAlgumas regras que o diferenciam do Delphi:
- É uma construção só: o Delphi tem
class helper forerecord helper for; aqui bastahelper for, porque o que muda entre um tipo e outro (terSelfde instância, ter VMT) já está no tipo estendido. Por dentro o helper é um record sem campos. - O helper ganha do tipo. Se os dois declaram o mesmo nome com a mesma assinatura, quem responde é o helper. É o contrário do Delphi, e é deliberado: um helper existe para substituir o comportamento de um tipo que não se pode editar.
- Com
overload, os dois conjuntos se juntam em vez de um esconder o outro. - O que o helper esconde não some:
inheritedalcança o método do tipo estendido, como numa classe alcança o do ancestral. - Um método
staticdo helper não recebeSelfe é chamado pelo nome do helper (TInt32Helper.FromText('40')), não pelo do tipo básico.
Atributos#
attribute Nome(params); declara a forma de um atributo, e [Nome(args)] decora tipos, campos, properties e métodos. Eles vão para o RTTI externo (<saída>.rtti.json, ligado pela opção Rtti), não para o binário, e alimentam os meta modules.
program Attributes;
// Atributos: lidos pelo compilador, conferidos contra a declaração e gravados
// só no RTTI externo (<saída>.rtti.json). Nada disso entra no binário, e é
// por isso que um meta module consegue lê-los em compilação.
attribute DbTable(const TableName: LString);
attribute DbField(const FieldName: LString);
type
[DbTable('CUSTOMERS')]
TCustomer = class(TObject)
private
[DbField('ID')]
FId: Int32;
public
// DefReadWrite sintetiza os acessores sobre o campo F<Nome>
[DbField('NAME')]
property Name: UString DefReadWrite;
end class;
var
C: TCustomer;
begin
C := TCustomer.Create;
C.Name := 'Ana';
WriteLn(C.Name);
end.Ana