delphi - ¿Cuáles son los buenos usos para los ayudantes de clase?
class helper (8)
Al principio era un poco escéptico sobre los ayudantes de clase. Pero luego leí una entrada de blog interesante y ahora estoy convencido de que son realmente útiles.
Por ejemplo, si desea una funcionalidad adicional para una clase de instancia existente y por algún motivo, no puede cambiar la fuente existente. Puede crear un ayudante de clase para agregar esta funcionalidad.
Ejemplo:
type
TStringsHelper = class helper for TStrings
public
function IsEmpty: Boolean;
end;
function TStringsHelper.IsEmpty: Boolean;
begin
Result := Count = 0;
end;
Cada vez, ahora usamos una instancia de (una subclase de) TStrings, y TStringsHelper está dentro del alcance. Tenemos acceso al método IsEmpty.
Ejemplo:
procedure TForm1.Button1Click(Sender: TObject);
begin
if Memo1.Lines.IsEmpty then
Button1.Caption := ''Empty''
else
Button1.Caption := ''Filled'';
end;
Notas:
- Los ayudantes de clase pueden almacenarse en una unidad separada, por lo que puede agregar sus propios ayudantes de clase ingeniosos. Asegúrate de dar a estas unidades un nombre fácil de recordar como ClassesHelpers para ayudar a la unidad de Clases.
- También hay ayudantes de registro.
- Si hay múltiples ayudantes de clase dentro del alcance, se esperan algunos problemas, solo se puede usar un ayudante.
Delphi (y probablemente muchos otros idiomas) tiene ayudantes de clase. Estos proporcionan una forma de agregar métodos extra a una clase existente. Sin hacer una subclase.
Entonces, ¿cuáles son los buenos usos para los ayudantes de clase?
Como muestra GameCat, TStrings es un buen candidato para evitar escribir algo:
type
TMyObject = class
public
procedure DoSomething;
end;
TMyObjectStringsHelper = class helper for TStrings
private
function GetMyObject(const Name: string): TMyObject;
procedure SetMyObject(const Name: string; const Value: TMyObject);
public
property MyObject[const Name: string]: TMyObject read GetMyObject write SetMyObject; default;
end;
function TMyObjectStringsHelper.GetMyObject(const Name: string): TMyObject;
var
idx: Integer;
begin
idx := IndexOf(Name);
if idx < 0 then
result := nil
else
result := Objects[idx] as TMyObject;
end;
procedure TMyObjectStringsHelper.SetMyObject(const Name: string; const Value:
TMyObject);
var
idx: Integer;
begin
idx := IndexOf(Name);
if idx < 0 then
AddObject(Name, Value)
else
Objects[idx] := Value;
end;
var
lst: TStrings;
begin
...
lst[''MyName''] := TMyObject.Create;
...
lst[''MyName''].DoSomething;
...
end;
¿Alguna vez necesitó acceder a cadenas de múltiples líneas en el registro?
type
TRegistryHelper = class helper for TRegistry
public
function ReadStrings(const ValueName: string): TStringDynArray;
end;
function TRegistryHelper.ReadStrings(const ValueName: string): TStringDynArray;
var
DataType: DWord;
DataSize: DWord;
Buf: PChar;
P: PChar;
Len: Integer;
I: Integer;
begin
result := nil;
if RegQueryValueEx(CurrentKey, PChar(ValueName), nil, @DataType, nil, @DataSize) = ERROR_SUCCESS then begin
if DataType = REG_MULTI_SZ then begin
GetMem(Buf, DataSize + 2);
try
if RegQueryValueEx(CurrentKey, PChar(ValueName), nil, @DataType, PByte(Buf), @DataSize) = ERROR_SUCCESS then begin
for I := 0 to 1 do begin
if Buf[DataSize - 2] <> #0 then begin
Buf[DataSize] := #0;
Inc(DataSize);
end;
end;
Len := 0;
for I := 0 to DataSize - 1 do
if Buf[I] = #0 then
Inc(Len);
Dec(Len);
if Len > 0 then begin
SetLength(result, Len);
P := Buf;
for I := 0 to Len - 1 do begin
result[I] := StrPas(P);
Inc(P, Length(P) + 1);
end;
end;
end;
finally
FreeMem(Buf, DataSize);
end;
end;
end;
end;
Esto se parece mucho a los métodos de extensión en C # 3 (y VB9). El mejor uso que he visto para ellos son las extensiones de IEnumerable<T>
(y IQueryable<T>
) que permiten que LINQ trabaje contra secuencias arbitrarias:
var query = someOriginalSequence.Where(person => person.Age > 18)
.OrderBy(person => person.Name)
.Select(person => person.Job);
(o lo que sea, por supuesto). Todo esto es factible porque los métodos de extensión le permiten encadenar llamadas de manera efectiva a métodos estáticos que toman el mismo tipo que regresan.
La primera vez que recuerdo haber experimentado lo que llamas "ayudantes de clase" fue al aprender Objective C. Cocoa (el marco Objective C de Apple) usa lo que se llama "Categorías".
Una categoría le permite extender una clase existente agregando sus propios métodos sin subclases. De hecho, Cocoa lo alienta a evitar las subclases siempre que sea posible. A menudo tiene sentido crear una subclase, pero a menudo se puede evitar utilizando categorías.
Un buen ejemplo del uso de una categoría en Cocoa es lo que se denomina "Código de valor clave (KVC)" y "Observación de valor clave (KVO)".
Este sistema se implementa utilizando dos categorías (NSKeyValueCoding y NSKeyValueObserving). Estas categorías definen e implementan métodos que se pueden agregar a cualquier clase que desee. Por ejemplo, Cocoa agrega "conformidad" a KVC / KVO al usar estas categorías para agregar métodos a NSArray como por ejemplo:
- (id)valueForKey:(NSString *)key
La clase NSArray no tiene una declaración ni una implementación de este método. Sin embargo, a través del uso de la categoría. Puede llamar a ese método en cualquier clase NSArray. No es necesario que subclass NSArray para obtener la conformidad KVC / KVO.
NSArray *myArray = [NSArray array]; // Make a new empty array
id myValue = [myArray valueForKey:@"name"]; // Call a method defined in the category
El uso de esta técnica facilita agregar soporte KVC / KVO a sus propias clases. Las interfaces Java le permiten agregar declaraciones de métodos, pero las categorías le permiten agregar las implementaciones reales a las clases existentes.
Los estoy usando:
- Para insertar enumeradores en las clases de VCL que no los implementan.
- Para enhance clases de VCL.
Para agregar métodos a la clase TStrings, puedo usar los mismos métodos en mis listas derivadas y en TStringList.
TGpStringListHelper = class helper for TStringList public function Last: string; function Contains(const s: string): boolean; function FetchObject(const s: string): TObject; procedure Sort; procedure Remove(const s: string); end; { TGpStringListHelper }
Para simplificar el acceso a los campos de registro y eliminar el casting .
Los he visto utilizar para hacer que los métodos de clase disponibles sean coherentes en todas las clases: agregar Abrir / Cerrar y Mostrar / Ocultar a todas las clases de un "tipo" dado en lugar de solo propiedades Activas y Visibles.
No recomendaría su uso, ya que leí este comentario:
"El mayor problema con los ayudantes de clase, desde el punto de vista de usarlos en sus propias aplicaciones, es el hecho de que solo UN ayudante de clase para una clase dada puede estar en el alcance en cualquier momento". ... "Es decir, si tiene dos ayudantes en el alcance, el compilador solo reconocerá a UNO. No obtendrá advertencias ni siquiera pistas sobre otros ayudantes que puedan estar ocultos".
http://davidglassborow.blogspot.com/2006/05/class-helpers-good-or-bad.html
Son muy útiles para los complementos. Por ejemplo, supongamos que su proyecto define una cierta estructura de datos y se guarda en el disco de cierta manera. Pero luego algún otro programa hace algo muy similar, pero el archivo de datos es diferente. Pero no desea hinchar su EXE con un montón de código de importación para una característica que muchos de sus usuarios no necesitarán usar. Puede usar un marco de plugins y colocar a los importadores en un complemento que funcionaría así:
type
TCompetitionToMyClass = class helper for TMyClass
public
constructor Convert(base: TCompetition);
end;
Y luego define el convertidor. Una advertencia: un ayudante de clase no es un amigo de la clase. Esta técnica solo funcionará si es posible configurar completamente un nuevo objeto TMyClass a través de sus métodos y propiedades públicas. Pero si puedes, funciona muy bien.