¿Cuáles son los pros y los contras de usar interfaces en Delphi?
(9)
¡Todo lo que digo es que las interfaces SIN el recuento de referencias son MUY ALTAS en mi lista de deseos para Delphi!
-> El uso real de las interfaces es la declaración de una interfaz. ¡No la capacidad de contar referencias!
He usado clases de Delphi desde hace un tiempo, pero nunca llegué a usar interfaces. Ya he leído un poco sobre ellos, pero quiero aprender más.
Me gustaría saber qué ventajas y desventajas se han encontrado al utilizar interfaces en Delphi con respecto a la codificación, el rendimiento, el mantenimiento, la claridad del código, la separación de capas y, en términos generales, cualquier aspecto que se le ocurra.
Gracias y un saludo
Agregando a las respuestas algunas ventajas más:
- Use interfaces para representar el comportamiento y cada implementación de un comportamiento implementará la interfaz.
- Publicación API: las interfaces son geniales para usar cuando se publican API. Puede publicar una interfaz sin dar la implementación real. Por lo tanto, puede realizar cambios estructurales internos sin causar ningún problema a los clientes.
El único caso en el que tuvimos que usar interfaces (además del material COM / ActiveX) fue cuando necesitábamos herencia múltiple y las interfaces eran la única forma de obtenerlo. En otros casos, cuando intentamos usar interfaces, tuvimos varios tipos de problemas, principalmente con el recuento de referencias (cuando se accedía al objeto tanto como una instancia de clase como a través de una interfaz).
Así que mi consejo sería usarlos solo cuando sabes que los necesitas , no cuando piensas que pueden hacerte la vida más fácil en algún aspecto.
Actualización: Como David recordó, con las interfaces se obtiene herencia múltiple de interfaces solamente, no de implementación. Pero eso estuvo bien para nuestras necesidades.
Hay algunas desventajas SUTIENTES en las interfaces que no sé si las personas consideran al usarlas:
La depuración se vuelve más difícil. He visto muchas dificultades extrañas para acceder a las llamadas a métodos interconectados, en el depurador.
Las interfaces en Delphi vienen con una semántica desconocida, si te gusta o no, estás atascado con el conteo de referencias como una interfaz compatible. Y, por lo tanto, con cualquier interfaz creada en el mundo de Delphi, debe asegurarse de manejar el recuento de referencias correctamente, y si no lo hace, terminará con pérdidas. Cuando desee evitar el recuento de referencias, su única opción es anular addref / decref y no liberar nada, pero esto no está exento de problemas. Encuentro que las bases de datos con mayor carga de interfaz tienen algunas de las violaciones de acceso y de memoria más difíciles de encontrar, y esto es, creo, porque es muy difícil combinar la semántica de refcount y la semántica delphi predeterminada (propietario libera objetos, y nadie más lo hace, y la mayoría de los objetos viven durante toda la vida de sus padres).
Las implementaciones mal hechas que usan interfaces pueden contribuir a algunos olores desagradables de código. Por ejemplo, las interfaces definidas en la misma unidad que define la implementación concreta inicial de una clase, agregan todo el peso de las interfaces, sin proporcionar realmente una separación adecuada entre los usuarios de las interfaces y los implementadores. Sé que esto no es un problema con las interfaces en sí, sino más bien una objeción con quienes escriben código basado en la interfaz. Por favor, ponga sus declaraciones de interfaz en unidades que solo tengan esas declaraciones de interfaz en ellas, y evite el infierno de dependencia de unidad a unidad causado por glomming sus declaraciones de interfaz en las mismas unidades que sus clases de implementador.
Las interfaces resuelven un cierto tipo de problemas. La función principal es ... bueno, ... definir interfaces. Para distinguir entre definición e implementación.
Cuando desee especificar o verificar si una clase admite un conjunto de métodos, use interfaces.
No puedes hacer eso de otra manera.
(Si todas las clases heredan de la misma clase base, entonces una clase abstracta definirá la interfaz. Pero cuando se trata de jerarquías de clases diferentes, se necesitan interfaces para definir los métodos que tienen en común ...)
Más allá de lo que otros ya enumeraron, un gran pro de las interfaces es la capacidad de agregarlas.
Escribí una publicación de blog sobre ese tema hace un tiempo que se puede encontrar aquí: http://www.nexusdb.com/support/index.php?q=intf-aggregation (tl; dr: puede tener múltiples objetos cada uno implementando una interfaz y luego ensamblarlos en un agregado que para el mundo exterior parece un solo objeto que implementa todas estas interfaces)
También es posible que desee echar un vistazo a las publicaciones "Fundamentos de la interfaz" y "Uso avanzado de la interfaz y patrones" vinculados allí.
Nota adicional sobre Contras: rendimiento
Creo que mucha gente descarta despreocupadamente la penalización de rendimiento de las interfaces. (No es que no me guste y uso interfaces, pero debe saber en qué se está metiendo). Las interfaces pueden ser costosas no solo para el golpe de _AddRef / _Release (incluso si solo está devolviendo -1) sino también que las propiedades REQUIEREN tener un método Get. En mi experiencia, la mayoría de las propiedades en una clase tienen acceso directo para el acceso de lectura (por ejemplo, propiedad Prop1: Integer read FProp1 write SetProp1). Cambiar ese acceso directo, sin penalización a una llamada a función puede afectar significativamente su velocidad (especialmente cuando comienza a agregar 10s de llamadas de propiedad dentro de un bucle).
Por ejemplo, un bucle simple usando una clase
for i := 0 to 99 do
begin
j := (MyClass.Prop1 + MyClass.Prop2 + MyClass.Prop3) / MyClass.Prop4;
MyClass.Update;
// do something with j
end;
pasa de 0 llamadas a funciones a 400 llamadas a funciones cuando la clase se convierte en una interfaz. Agregue más propiedades en ese ciclo y empeora rápidamente.
La penalización de _AddRef / _Release puede mejorarse con algunos consejos (estoy seguro de que hay otros consejos. Esto está fuera de mi cabeza):
- Utilice WITH o asigne a una variable temp para incurrir solo en la penalización de un _AddRef / _Release por bloque de código
- Siempre pase las interfaces usando la palabra clave const en una función (de lo contrario, obtendrá un _AddRef / _Release adicional cada vez que se llame a esa función.
Todo lo que puedo pensar por ahora:
Pros:
- Separación clara entre interfaz e implementación
- Dependencias reducidas de la unidad
- Herencia múltiple
- Recuento de referencias (si se desea, se puede deshabilitar)
Contras:
- Las referencias de clase e interfaz no se pueden mezclar (al menos con el recuento de referencias)
- Funciones Getter y setter requeridas para todas las propiedades
- El recuento de referencias no funciona con referencias circulares
- Problemas de depuración (gracias a Gabr y Warren por señalar eso)
Uso principalmente interfaces cuando quiero que objetos con diferentes ancestros ofrezcan un servicio común. El mejor ejemplo que puedo pensar desde mi propia experiencia es una interfaz llamada IClipboard
:
IClipboard = interface
function CopyAvailable: Boolean;
function PasteAvailable(const Value: string): Boolean;
function CutAvailable: Boolean;
function SelectAllAvailable: Boolean;
procedure Copy;
procedure Paste(const Value: string);
procedure Cut;
procedure SelectAll;
end;
Tengo un montón de controles personalizados derivados de los controles VCL estándar. Cada uno implementa esta interfaz. Cuando una operación del portapapeles alcanza uno de mis formularios, busca ver si el control activo admite esta interfaz y, de ser así, distribuye el método apropiado.
Para una interfaz muy simple, puede hacer esto con un controlador de eventos of object
, pero una vez que se vuelve lo suficientemente complejo, una interfaz funciona bien. De hecho, creo que es un muy buen análogo. Use una interfaz donde un solo evento of object
no se ajustará a la funcionalidad.