27 de diciembre de 2014

Abrir tablas o no

Abrir tablas o no abrir tablas. Esa es la pregunta.

por Jim Booth (Publicado originalmente en FoxTALK Diciembre 1998)
Traducido por Roberto Alfredo Moré
¿Se ha preguntado alguna vez si hay algún beneficio abriendo las tablas para un comando SQL SELECT antes de ejecutar el SELECT?. ¿Usa el SELECT las tablas que ya están abiertas?. Interesante pregunta.

¿Qué tan rápido es rápido?

Una simple prueba para averiguar cuál es el efecto de abrir primero las tablas sobre la performance de SQL en Visual FoxPro se muestra en el siguiente ejemplo de código.


LPARAMETERS plCloseThem
DIMENSION laTimes(100)
FOR lnCnt = 1 TO 100
lnStart = SECONDS()
SELECT * FROM BigFile WHERE cState = "NY" INTO CURSOR Result
lnEnd = SECONDS()
laTimes(lnCnt) = lnEnd - lnStart
IF plCloseThem
CLOSE ALL
ENDIF
ENDFOR
* Calcular el tiempo promedio
lnTime = 0
FOR lnCnt = 1 TO 100
lnTime = lnTime + laTimes(lnCnt)
ENDFOR
lnTime = lnTime/100
?lnTime

En el programa de prueba de arriba, la tabla BigFile es una tabla de 200,000 registros con índices sobre el campo cState y sobre DELETED(). El programa puede ser llamado con un parámetro que le indica si cierra o no las tablas entre las corridas del comando SELECT. Ejecuta el SELECT 100 veces y luego promedia el tiempo que toma el SELECT.
En mi máquina (Pentium II 300MHz con 128 MB RAM y un disco rígido SCSI) obtuve un promedio de tiempo de 1.23 segundos cuando las tablas se cierran entre los SELECTS. Si las tablas se dejan abiertas, el tiempo promedio fue de 0.67 segundos, ¡dos veces más rápido!.
Conclusión: Abra sus tablas primero y déjelas abiertas, especialmente si estará ejecutando múltiples SELECTS que acceden a estas tablas.

¿Usa realmente el SELECT las tablas que están abiertas?

No, no las usa. Utiliza un USE AGAIN para abrir la tabla en otra área de trabajo. Para ver esto, usted puede realizar la siguiente prueba:

1. Abra la ventana Data Session desde el menú Window.
2. En la ventana de comandos ejecute un comando SELECT simple como SELECT * FROM AlgunaTabla INTO CURSOR Resultado.
3. En la ventana Data Session haga click sobre el nombre de la tabla origen y observe el eco del comando en la ventana de comandos (será SELECT 1 o algo así). El número es el área de trabajo en que el cursor está abierto.
4. Haga click sobre el cursor Resultado y observe el área de trabajo (podría ser 2).
5. Tipee USE en la ventana de comandos para cerrar el cursor Resultado.
6. Corra nuevamente el comando SELECT.
7. Repita los pasos 3. y 4. nuevamente.
Encontrará que la segunda vez hay un hueco en las áreas de trabajo que usó el comando SELECT. Si usted hace esto cuando no hay ninguna tabla abierta, la primera vez las áreas de trabajo deberían ser 1 para la tabla origen y 2 para el resultado. La segunda vez, serían 1 para la tabla origen y 3 para el resultado.
La razón por la que el resultado está en un área de trabajo diferente la segunda vez es que el SELECT abrió la tabla origen en el área 2 durante el proceso de consulta. Esto fue hecho con una operación USE AGAIN. El beneficio de este comportamiento son dos cosas:

1) el segundo SELECT se ejecutará más rápido y
2) el puntero de registro en el área de trabajo de la tabla origen no se afecta.
¿Por qué es más rápido abrir las tablas?

Porque cuando una tabla está abierta, con SET OPTIMIZE ON, VFP trata de capturar tanto como pueda del índice para proveer a Rushmore con lo que necesita. Así, cada vez que se abre una tabla, se lee en memoria una cierta cantidad de información. Cuando se usa USE AGAIN para una tabla que ya está abierta, esa información ya está en memoria y no necesita ser leída del disco. Usted habrá notado, en el programa de prueba, que no hay un comando USE Bigfile. Esto ilustra otra sutil cualidad del comando SQL SELECT. Si se ejecuta el SELECT y la(s) tabla(s) de origen no está(n) abierta(s), la(s) abrirá y la(s) mantendrá abierta(s). Si la(s) tabla(s) de origen está(n) abierta(s), las usará nuevamente en otra área de trabajo y cerrará dicha área de trabajo cuando se completa el SELECT.

Una nota final.

Hay una preocupación reciente sobre el uso de SYS(2015) para generar nombres únicos de cursor. Existe un informe sobre que SUBSTR(SYS(2015),3) comenzará con un dígito en un futuro cercano. Bueno, esto es cierto, pero es irrelevante. Los cursores creados con el comando SQL SELECT o CREATE CURSOR usan el nombre para definir el alias del área de trabajo donde se ubica el cursor, NO el nombre del archivo en disco usado por el cursor para sus datos. Los nombres de archivo para los cursores serán siempre únicos y se crearán en el directorio temporal del usuario. Los nombres alias son siempre locales al equipo actual y no necesitan ser únicos. Pruebe lo siguiente en una ventana de comandos:

CREATE CURSOR MyCsr (Test C(10))
? ALIAS()
?DBF()

Lo que se obtiene como resultado de Alias() es MyCsr y DBF() es C:Temp2E4H000C.TMP. De aquí se puede deducir que
1) el archivo para el cursor fue creado en el directorio temporal, no en el disco de red, y
2) el nombre utilizado en el comando CREATE CURSOR no es el mismo que el que se usó en el archivo dbf.
No es necesario usar ningún algoritmo para crear un nombre único de un cursor; usted puede usar nombres significativos en su código y no encontrará problemas en un entorno multiusuario.

13 de diciembre de 2014

Introducción a los Patrones de diseño

Artículo original: Introduction to Design Patterns
http://weblogs.foxite.com/andykramek/2006/12/02/introduction-to-design-patterns/
Autor: Andy Kramek
Traducido por: Ana María Bisbé York


¡ Cuánto tiempo !

Hace ya tiempo que no he podido actualizar mi blog y existen varias razones para ello, no solamente se debe a que he estado extremadamente ocupado profesionalmente, trabajando en varios proyectos interesantes. Más recientemente, Marcia y yo viajamos a Frankfurt a hablar en la 13ra European DevCon donde nos unimos a Craig Bernston, Doug Henning, Lisa Slater-Nicholls y Rick Schummer.

Esta fue, como siempre, una G R A N conferencia y mantuvo el misterio de siempre, para todos nosotros los que hemos presenciado este evento, la poca cantidad de personas de EEUU que han estado. Los vuelos son (relativamente) baratos y agradables, y la oportunidad de presenciar una conferencia genuinamente buena combinado con un descanso europeo y recreación, es sencillamente una buena idea. Puede encontrar detalles de lo que se perdió en varios lugares de la red, incluyendo la cobertura en Universal Thread y los blogs de algunos presentes como Craig, Doug y Rick.

Tengo en plan comenzar otra pequeña serie de artículos en mi blog, justo ahora, con el tema de patrones de diseño. Esto es algo que me interesa desde hace ya tiempo (vea "The Revolutionary Guide to Visual FoxPro OOP", Wrox Press, 1996 escrito por Will Phelps, Bob Groomes y yo, con algunos ejemplos iniciales basados en patrones de diseño con VFP), y a juzgar por los comentarios y discuciones que he presenciado en conferencias, no soy el único interesado. La pregunta más común que he escuchado es "¿Cómo utilizo los Patrones de diseño en VFP?" y esto es lo que trataré de enfocar con esta pequeña serie de artículos - al menos para algunos de los patrones más comunes.

¿Porqué me debo preocupar por los Patrones de diseño?

Los patrones de diseño ofrecen un lenguaje estándar para reconocer, definir, y describir soluciones a problemas de la creación de aplicaciones. El conocimiento de los patrones de diseño, hace más fácil entender los sistemas existentes y describir los requerimientos para nuevos sistemas complejos.

Recuerdo que hace unos años, sentado con Paul Maskens en el lobby de Lindner Conference Hotel en Frankfurt, mientras el me explicaba una idea para la columna Kitbox. El estaba describiendo una solución a un problema que había desarrollado y luego de aproximadamente 20 minutos, de pronto, me di cuenta de que estaba hablando de el "patrón de estrategia". El comenzó diciendo que había implementado el patrón de estrategia quise saber inmediatamente qué uso general tenía aquí, y el método que había empleado para solucionarlo. Esto nos ahorró mucho tiempo y esfuerzo y nos permitió concentrarnos en los detalles de la implementación.

Sin embargo, es importante indicar que los patrones de diseño, no son, de por sí, la solución a problemas específicos. Son vías sencillas de identificación de los problemas y una descripción genérica de soluciones, que han sido probadas por la experiencia. La implementación real de los patrones de diseño sigue siendo el trabajo de un desarrollador de aplicaciones.

¿Qué son patrones de diseño?

Antes de comenzar a bucear en ejemplos específicos de cómo implementar patrones de diseño específicos en Visual FoxPro, debo comenzar por definir qué entendemos por "Patrones de Diseño". Se han propuesto varias definiciones y quizás la más reconocida es la que aparece en el trabajo "Design Patterns, Elements of Reusable Object-Oriented Software" (Diseño de patrones, elementos de reutilización de aplicaciones orientadas a objetos) escrito por Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides (comúnmente conocidos por la pandilla de los cuatro o simplemente como "GoF"). Ellos ofrecen, en el primer capítulo de su libro "What is a Design Pattern" (¿Qué es el diseño de patrones?), la siguiente definición:

Un diseño de patrones nombra, abstrae e identifica los aspectos claves de una estructura de diseño común que lo hace útil para la creación de un diseño orientado a objetos reutilizables. El diseño de patrones identifica la participación de clases e instancias, sus roles y colaboraciones, y la distribución de responsabilidades.

Esta es una buena definición, porque encapsula los cuatro elementos de cualquier diseño de patrones.

  • Tiene un nombre. Esto es vital, porque permite a los desarrolladores superar uno de los problemas fundamentales del diseño de aplicaciones – cómo comunicar lo que haces a otros? El ejemplo de Paul y yo discutiendo una idea para un artículo, que he mostrado antes, ilustra este punto perfectamente.
  • Abstrae el problema. Esto es referido por "GoF" como el "objetivo". Nos dice la naturaleza del problema y la solución descrita por el patrón. Considere el problema frecuente de advertir a los usuarios de no crear múltiples instancias de una aplicación. Los usuarios, por lo general, tratan de comenzar nuevas instancias de la aplicación porque, al tener minimizada la pantalla principal, se olvidan que está activa y hace clic en el icono del escritorio, en lugar de de maximizar la instancia existente. Podemos ver inmediatamente que el patrón Singleton es relevante porque su objetivo es "Asegurar una clase que tiene una sola instancia y provee un punto global de acceso a ella".
  • Define una estructura de diseño. Es importante darse cuenta que los patrones de diseño no ofrecen soluciones a los problemas. Ellos describen estructuras que permiten solucionar un problema de manera que resulte más fácil de reutilizar que si se escribe simplemente el código para solucionar el problema en el contexto en el que surge. Todos hemos pasado por estas experiencias de darnos cuenta que hemos solucionado ya un problema particular en algún lugar en nuestro código, pero no podemos re-utilizarlo, porque no hicimos una solución aislada de la situación. El objetivo de los patrones de diseño es ayudarnos a reconocer situaciones como estas y evitarlas.
  • Identifica la distribución de responsabilidades. Esto es, por supuesto, la clave a todos los tópicos de diseño y no está limitado a los patrones de diseño. Después de todo, una vez conocido lo que tiene que hacer una clase (u objeto), escribir el código para hacerlo es relativamente fácil. La ventaja de los patrones de diseño es que una vez que haya reconocido el problema y lo haga corresponder con un patrón, el patrón nos dirá cómo debemos asignar las responsabilidades y por consiguiente nos ayudará a crear rápidamente le "mejor" solución.

No es mi intención ofrecer una revisión exhaustiva de todos los patrones de diseño conocidos (existen libros enteros dedicados a eso). Pero en esta pequeña serie mi plan es cubrir los patrones encontrados más frecuentemente y mostrar cómo puede utilizar el Visual FoxPro para implementar una solución. Cada artículo es independiente y cubrirá un patrón, aunque intentaré en lo posible mantener un hilo único en ellos.

Espero que encuentre útil esta información, y si tiene cualquier comentario, por favor, sea libre de publicarlo.

5 de diciembre de 2014

Cualquier dirección con StreetView !!!

Artículo original: StreetView any address !!!
http://vfpimaging.blogspot.com.br/2014/12/streetview-any-adress.html
Autor: Cesar VFPIMAGING
Traducido por: Luis María Guayán


Acabo de recibir un pedido de un usuario que le permita ver la Vista de la Calle (Google StreetView) de nuestros clientes en los formularios de clientes.

Esto es útil especialmente para los casos de entregas, lo que permite al equipo saber el aspecto de la calle, y planificar mejor las entregas de mercaderías.

He encontrado varios ejemplos, pero todos ellos necesitaba las coordenadas GPS (Global Positioning System - Sistema de Posicionamiento Global), la latitud y la longitud para hacer que funcione.

Después de hacer algunas búsquedas, he encontrado la solución en el BLOG JAYCODE DESIGN, de Jordan Clist.
"A todos nos gusta el servicio Streetview que ofrece Mapas de Google. Naturalmente, la API de Google ofrece la posibilidad de incrustar Streetview dentro de una aplicación web, si le das los datos y coordenadas correctas.
Lo qué no hace de forma natural y fácil es apuntar la cámara StreetView automáticamente a una dirección.
Esto es algo que pensé que sería fácil, pero en realidad era más difícil de lo que parece.
Aunque es bastante simple crear una vista panorámica de la calle en un punto GPS dado, lo que es más complicado es asegurarse de que la cámara StreetView esté apuntando en la dirección correcta (punto de vista, u orientación)"
Por lo que es el trabajo era un poco complicado, ya que el ejemplo original proporcionado trabajó solamente con algunas direcciones ya conocidos por Google. Eso hizo que falle en la mayoría de los casos, debido a la ortografía, o incluso una coma puesta en el lugar equivocado.

Así que usé otra API de Google Maps, la "Google Maps Directions API", que se utiliza originalmente para decirnos las rutas que podemos tomar entre 2 direcciones. Esta API funciona muy bien con las direcciones sin un buen formato, y devuelve la versión de Google de la dirección. Adapté un ejemplo de Mike Gagnon, de su artículo publicado en http://atoutfox.org "Calcular la distancia entre 2 direcciones, y mostrar el recorrido completo"

Después de conseguir esta dirección, lo paso al script  de Jordan Clist y Voila! Funciona !

Copiar y pegar el siguiente código y guardarlo en un archivo .PRG y llamado "GOOGLESTREETVIEW.PRG"

Y para probarlo, escriba en la ventana de comandos:

=GoogleStreetView("Arche du Triumph") 

o, por supuesto, pasando la dirección completa:

=GoogleStreetView("Place Charles de Gaulle, 75008, Paris, France")

Bonito no es así?



1 de diciembre de 2014

Diseñar y crear clases

Artículo original: Designing and Building Classes
http://weblogs.foxite.com/andykramek/2005/03/27/designing-and-building-classes/
Autor: Andy Kramek
Traducido por: Ana María Bisbé York


He aquí otro tema sobre el que he sido preguntado con frecuencia y es precisamente cómo se deben diseñar las clases. Por supuesto, no hay una única respuesta "correcta"; pero he encontrado siempre muy útil al crear una clase nueva, preguntarme estas tres preguntas:

  • ¿Será reutilizado el objeto?
  • ¿La clase va a simplificar la tarea o a complicarla?
  • ¿Vale la pena el esfuerzo?

Ahora, para ganar en claridad, permítanme que me expanda un poco en esto:

Re-usabilidad

Este es, probablemente la razón más común para la creación de una clase y la posibilidad de re-utilizarla, es después de todo, uno de los logros primarios de POO. En su nivel más simple, puede significar tan sencillo como establecer sus preferencias a los objetos - propiedades Font, Color y Style, por ejemplo, de tal forma que todos los objetos de sus clases se creen con la configuración correcta. Las cosas se complican un poco cuando consideramos la funcionalidad. En la práctica, ¿con qué frecuencia hacemos exactamente lo mismo, en exactamente la misma forma, para alcanzar exactamente el mismo resultado? La respuesta es probablemente "no muy frecuentemente" y empezará a asombrarse si la re-usabilidad, es después de todo, realmente tan valiosa. Pasemos al segundo criterio.

Controlar complejidad

Realmente es muy raro que las clases funcionales puedan simplemente ser re-utilizadas "tal cual". Existen casi siempre, diferencias en el entorno, la entrada o salida de datos requeridos y a veces, todos juntos. Esta complejidad aparente puede a veces oscurecer el tema, con la funcionalidad que se desea mantener constante, incluso a través de la implementación, puede diferir en detalle entre las aplicaciones.

Al aplicar las reglas para el diseño de clases resumidas en el siguiente tópico, debe ser más fácil decidir si al utilizar una clase va a facilitar el control de esta complejidad o no. Incluso, si el criterio de una clase pueda facilitar la manipulación de la complejidad, nosotros aun tendremos que considerar el tercer criterio.

Vale la pena el esfuerzo

Un objeto debería ser encapsulado para que contenga en si mismo toda la información necesaria para completar su tarea, e incluso evidente que ningún objeto debe confiar su implementación interna a otro objeto. Claramente esto puede complicarnos mucho la vida. Puede significar que su clase tendrá que verificar docenas de condiciones posibles para determinar (para si) exactamente que el estado del sistema es antes de que pudiera realizar la función asignada. Al considerar la creación de una clase nueva, es importante estar seguros de que realmente vale la pena el esfuerzo de hacer tal cosa.

¿Qué tipo de clase queremos?

Al tener que decidir que realmente deseo crear una clase nueva, la siguiente pregunta a responder es: ¿Qué tipo de clase debo diseñar?" Existen dos modelos fundamentales para el diseño de clases que son el "Mundo Real" o un modelo concreto de "Componente" o modelo abstracto. No hereda uno del otro, son precisamente diferentes.

Modelo Mundo real (concreto)

En este modelo las clases se diseñan para cumplir la funcionalidad requerida en su totalidad. En este modelo se basan usualmente los Objetos de negocio. Por ejemplo, podemos definir a una clase objeto de negocios "persona" que tiene las características comunes (por ejemplo, propiedades para Ojo izquierdo, ojo derecho, nariz, etc) y el comportamiento de las personas (ejemplo Métodos de Respiración, Alimentación, etc). Esta clase persona puede ser sub-claseada para modelar la funcionalidad de tipos especiales de personas tales como "clientes" o "amigos". Es realmente aparente que el modelo real confía demasiado en la herencia y , como un resultado, en tiempo de diseño, está explícitamente definida la funcionalidad  .

Modelo Componente (abstracto)

En este modelo la clase se ocupa más de un comportamiento específico, que de funcionalidad. Cada clase define una función genérica o acción y, de manera aislada, limita la usabilidad. Claramente habrá más confianza en la herencia, en su lugar, el modelo va a depender de la agregación (esencialmente una operación en tiempo de ejecución) y la composición (en tiempo de ejecución y diseño). El equivalente para nuestra clase objeto de negocio Persona es un compuesto de objetos separados: dos instancias para las clases ojos y una instancia para la clase nariz, etc.

Identificar responsabilidades

Habiendo decidido qué modelo va a utilizar. la siguiente tarea es estar absolutamente seguro de que sabe lo que la clase va a hacer. Esto puede sonar obvio; pero hay una trampa sutil en esto. Es muy fácil crear demasiado en una clase, antes de que se de cuenta que lo que ha creado que en realidad no es re-utilizable, porque hace demasiado. El mejor proceder para esto es identificar y categorizar las "Responsabilidades" de la clase antes de que comience a escribir código alguno. Una responsabilidad puede estar definida, de forma muy simple, como algún elemento de funcionalidad que tiene que ser completado.

Categorizar responsabilidades (Las reglas "debe, podría haber y debe ser hecho").

El propósito de la categorización es asegurarse de que puedo identificar correctamente donde en la jerarquía de clases, pertenece cada responsabilidad. Yo hago esto al utilizar tres categorías las que llamo "debe hacer", "podría hacer" y "debe ser hecho"

Las responsabilidades que caen dentro de la primera categoría "debe hacer" son aquellas que derivan de un objeto desde la clase que debe hacer en cada situación y que no puede ser hecho por otro objeto. Esto es, claramente, la responsabilidad de la clase. Debe ser parte de la definición de clase raíz. Por ejemplo, cualquier objeto que mueve el puntero del registro en una tabla, cursor o vista, DEBE chequear las condiciones EOF() y BOF().

Las responsabilidades que entran en la categoría "podría hacer" son indicadores de que la clase puede requerir una o más subclases (o, posiblemente, la cooperación entre objetos de otra clase.) En otras palabras, estas son cosas que es posible que haga la clase; pero que no son precisamente requeridas en cada situación. Típicamente es un fallo reconocer aquí estos elementos que "podría hacer" muy pronto en el proceso de diseño que encamine a una inadecuada colocación de la funcionalidad en la jerarquía de clases y la selección sea hecha. Sin embargo, un objeto que muestre una lista PUEDE inhabilitarse a si mismo una vez que sea hecha la selección. Sin embargo, esto no es la función esencial y no es el comportamiento requerido en cada momento. Esto pertenece a una subclase especializada.

La categoría final (debe ser hecho) es muy importante también. Esta es la categoría que define la suposición de que una clase puede ser exitosa para funcionar correctamente. Los elementos listados definitivamente no son de única responsabilidad de la clase en cuestión pero pueden, aun así, ser hechos de alguna forma. Por ejemplo, para que un control enlace con un origen de datos, el origen de dato DEBE estar abierto antes de que sea instanciado el control. Sin embargo, controlar esta operación claramente no es responsabilidad del control como tal - si solamente porque debe estar hecho antes de que se instancie el control.

Teniendo definidas y categorizadas las responsabilidades de la nueva clase, puede entonces definir su interfaz pública decidiendo qué propiedades y métodos va a requerir y cómo se deben revelar a otros objetos con los que interactúan. Sólo cuando todo esto esté hecho, puede comenzar a pensar sobre el código y aquí hay un par de cosas que es necesario recordar.

Escribir código en los métodos, no en los eventos.

Al escribir el código de las clases, yo creo firmemente en evitar el colocar código directamente en los eventos nativos de Visual FoxPro, siempre que puedo hacerlo. En su lugar, creo métodos de usuario y los llamo desde los eventos. Hacerlo sí no es, admitámoslo, un requerimiento; pero encuentro que te facilita la vida por varias razones:

  • Me permite dar nombres más identificativos a los métodos. Esto puede sonar trivial; pero realmente hace que sea más fácil de leer y mantener el código cuando el código produce algún tipo de salida es llamado por: "This.GenerarSalida()" en lugar de "Thisform.Pageframe1.Page1.CmdButton1.Click()"
  • Me permite cambiar la interfaz, si es necesario con la simple re-colocación de una única línea que llama al método en lugar de de tener que cortar y pegar la parte funcional del código.
  • Me permite partir los métodos complejos en métodos múltiples (y potencialmente reutilizables). El evento actúa meramente como indicador para el código asociado.

Los métodos deben hacer una cosa y sólo una

Los métodos que hacen "de todo" son difíciles de reutilizar. Recuerde: una simple línea de código es siempre reutilizable. Si tengo que oprimir la tecla Re Pag más de una vez para ver mi código, lo más probable es que el método sea demasiado largo y trate de hacer demasiadas cosas. La solución es partir ese tipo de métodos monolíticos en métodos pequeños y mejor enfocados, y utilizar un método de control que los pueda llamar de forma individual si es necesario. (Pregúntese a si mismo cuántas veces se ha visto duplicando código en un método o bloque de código que ya existe en parte de otro método.

Asegúrese de poner la funcionalidad lo más alto posible en la jerarquía de clases

Esto es una trampa en la que he caído muchas veces a través de los años y aun ocasionalmente tropiezo. Debe saber que cuando coloca funcionalidad muy alto porque invierte mucho tiempo en sus subclases tratando de lograr toda una gran funcionalidad que pueda en la clase raíz. Uno de los mayores beneficios de esto "Puede, debe, podría" es el que ayuda a evitar esta trampa.

Utilice métodos plantilla para una interfaz inconsistente.

He constatado que necesitamos evitar colocar la funcionalidad en una jerarquía muy alta. Al crear un contenedor, vacío métodos en un nivel más alto de la jerarquía, puedo asegurar que todas las clases van a compartir una interfaz común. Ya que las subclases comparten la interfaz común, son intercambiables. Incluso, si un método vacío es llamado., no hace daño a nadie, porque todos los métodos de VFP devuelven .T. de forma predeterminada, incluso si no contienen texto.