Depende del problema, como siempre. Yo uso interfaces para definir la interfaz de usuario para el conjunto de clases. Al menos cuando sé que tendré más de una implementación de la clase real subyacente. Por ejemplo, puede tener algo como esto:
IAllInterfaced = interface(IInterface)
procedure ImplementMeEverywhere_1(const Params: TParams);
procedure ImplementMeEverywhere_2(const Params: TParams);
procedure ImplementMeEverywhere_3(const Params: TParams);
end;
TAllInterfaced_ClassA = class(TInterfacedObject, IAllInterfaced)
public
procedure ImplementMeEverywhere_1(const Params: TParams);
procedure ImplementMeEverywhere_2(const Params: TParams);
procedure ImplementMeEverywhere_3(const Params: TParams);
end;
TAllInterfaced_ClassB = class(TInterfacedObject, IAllInterfaced)
public
procedure ImplementMeEverywhere_1(const Params: TParams);
procedure ImplementMeEverywhere_2(const Params: TParams);
procedure ImplementMeEverywhere_3(const Params: TParams);
end;
Aquí no tiene un antepasado común. Cada clase solo implementa la interfaz y no tiene una estructura subyacente común en forma de una clase base común. Esto es posible si las implementaciones son tan diferentes que no comparten nada, pero sí la interfaz. Aún necesita usar la misma interfaz para que sea coherente con los usuarios de las clases derivadas.
La segunda opción es:
IAllAbstract = interface(IInterface)
procedure ImplementMeEverywhere_1(const Params: TParams);
procedure ImplementMeEverywhere_2(const Params: TParams);
procedure ImplementMeEverywhere_3(const Params: TParams);
end;
TAllAbstract_Custom = (TInterfacedObject, IAllAbstract)
private
...
public
procedure ImplementMeEverywhere_1(const Params: TParams); virtual; abstract;
procedure ImplementMeEverywhere_2(const Params: TParams); virtual; abstract;
procedure ImplementMeEverywhere_3(const Params: TParams); virtual; abstract;
end;
TAllAbstract_ClassA = class(TAllAbstract_Custom)
public
procedure ImplementMeEverywhere_1(const Params: TParams); override;
procedure ImplementMeEverywhere_2(const Params: TParams); override;
procedure ImplementMeEverywhere_3(const Params: TParams); override;
end;
TAllAbstract_ClassB = class(TAllAbstract_Custom)
public
procedure ImplementMeEverywhere_1(const Params: TParams); override;
procedure ImplementMeEverywhere_2(const Params: TParams); override;
procedure ImplementMeEverywhere_3(const Params: TParams); override;
end;
Aquí tienen una clase base para todas las clases. En esa clase puede tener propiedades comunes o eventos de otras clases, etc. ... Pero todos los procedimientos están marcados como abstractos porque no realizan ninguna tarea común. Abstract garantiza que se implementarán en las clases derivadas, pero no es necesario implementar "FieldA" en todas las clases, solo lo implementa en "TAllAbstract_Custom". Esto asegura que se usa el principio DRY.
La última opción es:
IAllVirtual = interface(IInterface)
procedure ImplementMeEverywhere_1(const Params: TParams);
procedure ImplementMeEverywhere_2(const Params: TParams);
procedure ImplementMeEverywhere_3(const Params: TParams);
end;
TAllVirtual_Custom = (TInterfacedObject, IAllVirtual)
private
...
public
procedure ImplementMeEverywhere_1(const Params: TParams); virtual;
procedure ImplementMeEverywhere_2(const Params: TParams); virtual;
procedure ImplementMeEverywhere_3(const Params: TParams); virtual;
end;
TAllVirtual_ClassA = class(TAllVirtual_Custom)
public
procedure ImplementMeEverywhere_1(const Params: TParams); override;
procedure ImplementMeEverywhere_2(const Params: TParams); override;
procedure ImplementMeEverywhere_3(const Params: TParams); override;
end;
TAllVirtual_ClassB = class(TAllVirtual_Custom)
public
procedure ImplementMeEverywhere_1(const Params: TParams); override;
procedure ImplementMeEverywhere_2(const Params: TParams); override;
procedure ImplementMeEverywhere_3(const Params: TParams); override;
end;
Aquí todas las clases derivadas tienen un procedimiento virtual de base común. Esto garantiza que no tenga que implementar todos los procedimientos en el nivel de las clases derivadas. Solo puede anular algunas partes del código o ninguna.
Naturalmente, estos son todos los casos de borde, hay espacio entre ellos. Puedes tener una mezcla de esos conceptos.
Sólo recuerde:
- interfaces son poderosa herramienta para garantizar que se oculta la aplicación por parte del usuario y que tiene un punto de uso común (interfaz). También obligan a algunas normas a ser utilizadas, porque la interfaz debe implementarse en su totalidad.
- El resumen es una buena herramienta, por lo que no tiene que usar los talones vacíos para los procedimientos que no los necesitan. Por otro lado, te obligan a implementarlos en clases derivadas.
- Virtual es útil cuando se tiene un código común que debe implementarse en todas las clases y que garantiza el principio limpio OP y DRY. También son bienvenidos cuando tienes procedimientos que no todas las clases derivadas tienen o necesitan.
Perdón por una respuesta larga, pero no pude dar una explicación fácil aquí porque no la hay. Todo depende del problema en cuestión. Es un equilibrio entre cuánto tienen en común las clases derivadas y cuán diferentes son sus implementaciones.
Me pregunto si mi refracción es, de hecho, refractar o simplemente entrometerme. En su mayor parte, sería más simple mantener el código como está y dejar que Delphi Autocomplete mis funciones con heredado y dejar que Delphi me advierta cuando una función abstracta no se implemente. La única razón por la que lo cambiaría es porque cuando agrego nuevas clases secundarias, estoy copiando y pegando una gran sección de interfaz (que no hace más que volverse más grande) –