Interfaz fluida en Delphi
fluent-interface (5)
Este es un tipo de notación de escritura, una vez, lectura, nunca, que no es fácil de entender sin pasar por la documentación de todos los métodos involucrados. Además, dicha notación no es compatible con las propiedades Delphi y C #: si necesita establecer propiedades, debe volver a utilizar notaciones comunes ya que no puede encadenar asignaciones de propiedades.
¿Cuáles son los pros y los contras para usar interfaces fluidas en Delphi?
Se supone que las interfaces fluidas aumentan la legibilidad, pero soy un poco escéptico de tener un LOC largo que contenga muchos métodos encadenados.
¿Hay algún problema con el compilador?
¿Hay algún problema de depuración?
¿Hay problemas de manejo del tiempo de ejecución / error?
Las interfaces TStringBuilder se utilizan, por ejemplo, en TStringBuilder , THTMLWriter y TGpFluentXMLBuilder .
Actualizado:
David Heffernan preguntó qué temas me preocupaban. Me lo han pensado un poco, y el problema general es la diferencia entre "especificar cómo se hace" y "dejar que el compilador decida cómo se hace".
AFAICS, no hay documentación sobre cómo el compilador realmente maneja los métodos encadenados, ni ninguna especificación sobre cómo debe tratar el compilador los métodos encadenados.
En este artículo podemos leer acerca de cómo el compilador agrega dos var-parameters adicionales a los métodos declarados como funciones, y que la convención de llamadas estándar pone tres params en el registro y los siguientes en la pila. Por lo tanto, un "método de función fluida" con 2 parámetros usará la pila, mientras que un "método de procedimiento ordinario" con 2 parámetros solo usa el registro.
También sabemos que el compilador hace algo de magia para optimizar el binario (por ejemplo, cadena como resultado de la función , orden de evaluación , ref al proc local ), pero a veces con efectos secundarios sorprendentes para el programador.
Por lo tanto, el hecho de que la gestión de memoria / pila / registro sea más compleja y el hecho de que el compilador pueda hacer algo de magia con los efectos colaterales involuntarios, es bastante mal para mí. De ahí la pregunta.
Después de leer las respuestas (muy buenas), mi preocupación se reduce fuertemente, pero mi preferencia sigue siendo la misma :)
Todo el mundo está escribiendo sobre cuestiones negativas, así que enfaticemos algunos aspectos positivos. Errr, el único problema positivo: menos tipeo (en algunos casos mucho menos).
Escribí GpFluentXMLBuilder solo porque odio escribir toneladas de código al crear documentos XML. Nada más y nada menos.
Lo bueno de las interfaces fluidas es que no tienes que usarlas con fluidez si odias la expresión idiomática. Son completamente utilizables de una manera tradicional.
EDITAR: Un punto para la vista de "brevedad y legibilidad".
Estaba depurando un código viejo y tropecé con esto:
fdsUnreportedMessages.Add(CreateFluentXml
.UTF8
.AddChild(''LogEntry'')
.AddChild(''Time'', Now)
.AddSibling(''Severity'', msg.MsgID)
.AddSibling(''Message'', msg.MsgData.AsString)
.AsString);
Supe de inmediato lo que hace el código. Sin embargo, si el código se viera así (y no estoy afirmando que esto siquiera compila, simplemente lo lancé para la demostración):
var
xmlData: IXMLNode;
xmlDoc : IXMLDocument;
xmlKey : IXMLNode;
xmlRoot: IXMLNode;
xmlDoc := CreateXMLDoc;
xmlDoc.AppendChild(xmlDoc.CreateProcessingInstruction(''xml'',
''version="1.0" encoding="UTF-8"''));
xmlRoot := xmlDoc.CreateElement(''LogEntry'');
xmlDoc.AppendChild(xmlRoot);
xmlKey := xmlDoc.CreateElement(''Time'');
xmlDoc.AppendChild(xmlKey);
xmlData := xmlDoc.CreateTextNode(FormatDateTime(
''yyyy-mm-dd"T"hh":"mm":"ss.zzz'', Now));
xmlKey.AppendChild(xmlData);
xmlKey := xmlDoc.CreateElement(''Severity'');
xmlDoc.AppendChild(xmlKey);
xmlData := xmlDoc.CreateTextNode(IntToStr(msg.MsgID));
xmlKey.AppendChild(xmlData);
xmlKey := xmlDoc.CreateElement(''Message'');
xmlDoc.AppendChild(xmlKey);
xmlData := xmlDoc.CreateTextNode(msg.MsgData.AsString);
xmlKey.AppendChild(xmlData);
fdsUnreportedMessages.Add(xmlKey.XML);
Necesitaría bastante tiempo (y una taza de café) para entender qué hace.
EDIT2:
Eric Grange hizo un punto perfectamente válido en los comentarios. En realidad, uno usaría algún contenedor XML y no DOM directamente. Por ejemplo, al usar OmniXMLUtils desde el paquete OmniXML , el código sería así:
var
xmlDoc: IXMLDocument;
xmlLog: IXMLNode;
xmlDoc := CreateXMLDoc;
xmlDoc.AppendChild(xmlDoc.CreateProcessingInstruction(
''xml'', ''version="1.0" encoding="UTF-8"''));
xmlLog := EnsureNode(xmlDoc, ''LogEntry'');
SetNodeTextDateTime(xmlLog, ''Time'', Now);
SetNodeTextInt(xmlLog, ''Severity'', msg.MsgID);
SetNodeText(xmlLog, ''Message'', msg.MsgData.AsString);
fdsUnreportedMessages.Add(XMLSaveToString(xmlDoc));
Aún así, prefiero la versión fluida. [Y nunca uso formateadores de código.]
Yo cuestionaría el beneficio de usar "interfaces fluidas".
Por lo que veo, el punto es permitirle evitar tener que declarar una variable. Entonces, el mismo beneficio trae la temida declaración de With, con un conjunto diferente de problemas (ver otras respuestas)
Honestamente, nunca entendí la motivación para usar la declaración With, y tampoco entiendo la motivación para usar interfaces fluidas. Quiero decir, ¿es tan difícil definir una variable? Todo este galimatías solo para permitir la pereza.
Yo diría que, en lugar de aumentar la legibilidad, que a primera vista parece tener que escribir / leer menos, en realidad lo ofusca.
Entonces, nuevamente, pregunto por qué querrías usar interfaces fluidas en primer lugar.
¿Fue acuñado por Martin Fowler así que debe ser genial? Nah, no lo estoy comprando.
Are there any compiler issues?
No.
Are there any debugging issues?
Sí. Dado que todas las llamadas a métodos encadenados se ven como una sola expresión, incluso si las escribe en varias líneas como en el ejemplo de Wikipedia que enlazó, es un problema al depurar porque no puede pasar por ellas en un solo paso.
Are there any runtime/error handling issues?
Editado : Aquí hay una aplicación de consola de prueba que escribí para probar la sobrecarga de Runtime real de usar interfaces fluidas. Asigné 6 propiedades para cada iteración (de hecho los mismos 2 valores 3 veces cada uno). Las conclusiones son:
- Con interfaces: 70% de aumento en el tiempo de ejecución, depende del número de propiedades establecidas. Al configurar solo dos propiedades, la sobrecarga era más pequeña.
- Con objetos: el uso de interfaces fluidas fue más rápido
- No probé los registros. ¡No puede funcionar bien con los registros!
Personalmente, no me importan esas "interfaces fluidas". Nunca he oído hablar del nombre antes, pero he estado usándolo, especialmente en el código que completa una lista del código. (Algo así como el ejemplo XML que publicaste). No creo que sean difíciles de leer, especialmente si uno está familiarizado con este tipo de codificación Y los nombres de los métodos son significativos. En cuanto a la larga línea de código, mira el ejemplo de Wikipedia: no necesitas ponerlo todo en una línea de código.
Recuerdo claramente usarlos con Turbo Pascal
para inicializar Pantallas, que es probablemente la razón por la cual no me molestan y también las uso a veces. Lamentablemente Google me falla, no puedo encontrar ningún ejemplo de código para el antiguo código TP.
Problemas del compilador:
Si está utilizando interfaces (en lugar de objetos), cada llamada en la cadena dará como resultado una sobrecarga de recuento de referencias, incluso si la misma interfaz se devuelve todo el tiempo, el compilador no tiene forma de saberlo. Así generará un código más grande, con una pila más compleja.
Problemas de depuración:
La cadena de llamadas se ve como una instrucción única, no se puede avanzar o un punto de interrupción en los pasos intermedios. Tampoco puedes evaluar el estado en pasos intermedios. La única manera de depurar los pasos intermedios es rastrear en la vista asm. La pila de llamadas en el depurador tampoco será clara si los mismos métodos se repiten varias veces en la cadena.
Problemas de tiempo de ejecución:
Cuando se utilizan interfaces para la cadena (en lugar de objetos), tiene que pagar la sobrecarga de recuento de referencia, así como un marco de excepción más complejo. No puede haber intentado ... finalmente construir en una cadena, por lo que no hay garantía de cerrar lo que se abrió en una cadena fluida fi
Problemas de registro de errores / errores:
Las excepciones y su seguimiento de pila verán la cadena como una sola instrucción, por lo que si se bloqueó en .DoSomething, y la cadena de llamadas tiene varias llamadas .DoSomething, no sabrá cuál fue la causa del problema.
Problemas de formato de código:
AFAICT ninguno de los formateadores de códigos existentes diseñará correctamente una cadena de llamadas fluida, por lo que es solo un formateo manual que puede mantener una cadena de llamadas fluida legible. Si se ejecuta un formateador automatizado, normalmente convertirá una cadena en un desastre de legibilidad.