Implementacion de interfaces en VFP
Por VictorEspina
Contents |
Introducción
Las interfaces son una tecnica muy utilizada en OOP para extender la funcionalidad de una clase sin hacer uso de la herencia, asi como para determinar en tiempo de ejecución si un objeto dado puede interactuar con otro objeto desconocido, determinando si dicho objeto implementa una interfaz conocida.
En términos generales, una interfaz define una serie de propiedades y metodos que una clase debe implementar para cumplir con dicha interfaz. Esto permite a objetos distintos interactuar de forna segura entre ellos sin necesidad de conocer completamente sus interfaces sino SOLO si implementan una interfaz común.
Visual FoxPro no soporta la declaración y uso de interfaces de forma nativa. Este articulo muestra una forma de implementar de forma parcial las bodandes del uso de interfaces en VFP 6.0 o superior, mediante el uso de una clase helper.
Clase InterfaceHelper
La clase InterfaceHelper ofrece una forma de declarar interfaces de forma sencilla (aunque limitada, ya que no es posible indicar la firma de los metodos, es decir, la lista de parámetros que espera cada metodo y el valor devuelto) y determinar si un objeto dado implementa una interfaz previamente declarada.
Supongamos que tenemos la siguiente clase:
DEFINE CLASS Usuarios AS Custom ENDDEFINE
En este punto, no tenemos forma de saber si una instancia de esa clase contiene "n" elementos ni como podemos recorrer dichos elementos. Es aqui donde entran las interfaces. Supongamos que declaramos una interfaz llamada IEnumerable que define las propiedades y metodos que debe implementar un objeto para poder recorrer su contenido:
PUBLIC goInterfaces goInterfaces = CREATE("interfaceHelper") goInterfaces.Declare("IEnumerable","count,items,@indexOf")
Lo que estamos diciendo aca es que existe una interfaz llamada IEnumerable que consiste de las propiedades Count y Items, asi como el metodo indexOf. Si ahora hacemos:
LOCAL oUsuarios oUsuarios = CREATE("Usuarios") ?goInterfaces.Implements(oUsuarios, "IEnumerable") --> .F.
Podremos ver que la clase Usuarios no ofrece una forma de recorrer su contenido, ya que no implementa la interfaz IEnumerable. Ahora, solucionemos ese problema:
DEFINE CLASS Usuarios AS Custom Count = 0 DIMEN Items[1] PROCEDURE indexOf(pcElement) ... ENDPROC ENDDEFINE
Como se ve, esta nueva version de la clase Usuarios implementa las propiedades y metodos indicados en IEnumerable, por lo que ahora podemos hacer esto sabiendo de antemano que no obtendremos un error en tiempo de ejecución:
LOCAL oUsuarios oUsuarios = CREATE("Usuarios") IF goInterfaces.Implements(oUsuarios, "IEnumerable") FOR i = 1 TO oUsuarios.Count oUsuario = oUsuarios.Items[i] ENDFOR ENDIF
Las ventajas de esto pueden aplicarse a todo tipo de objetos. Supongamos que queremos determinar si un objeto dado puede recibir o no el foco:
goInterfaces.Declare("IFocusable","@setFocus") IF goInterfaces.Implements(oObject, "IFocusable") oObject.setFocus() ENDIF
Caso especial para VFP 9
Si se esta usando VFP 9, la clase incluye un constructor adicional para la clase InterfaceInfo que permite definir una interfaz a partir de una clase. Por ejemplo, en lugar de declarar la interfaz IEnumerable manualmente, tal como vimos en los ejemplos anteriores, crearíamos una clase que herede de la clase especial Interface:
DEFINE CLASS IEnumerable AS Interface Count = 0 DIMEN Items[1] ENDDEFINE
Y luego, declaramos la interfaz en el helper usando el mismo metodo Declare:
goInterfaces.Declare("IEnumerable")
Como vemos, ya no seria necesario indicar la firma de la inrterfaz ya que la misma se obtendra directamente de la clase.
Codigo Fuente
* INTERFACEHELPER.PRG * Clase para soporte parcial de interaces en VFP 6.0 o superior * * Autor: Victor Espina * Fecha: Abril 2012 * * DEFINE CLASS interfaceHelper AS Custom * DIMEN Interfaces[1] interfaceCount = 0 * Constructor PROCEDURE Init DIMEN THIS.Interfaces[1] THIS.interfaceCount = 0 ENDPROC * Declare * Permite declarar una interfaz. Una interfaz se declara * indicando su nombre y su "firma", la cual consiste en una * lista de propiedades y metodos que deben implementar las * clases para ser compatibles con la interfaz. * * ej: * oInterfaces.Declare("IEnumerable","count,items,@indexOf") * * Los metodos se deben identificar con el prefijo @. * * NOTA PARA VFP 9: * Si se esta usando VFP 9, es posible declarar una interfaz * a partir de una clase existente de la siguiente forma: * * oInterfaces.Declare("className") * PROCEDURE Declare(pcName, pcSignature) LOCAL oInterface oInterface = CREATE("interfaceInfo") oInterface.Name = pcName IF VARTYPE(pcSignature) = "C" oInterface.initWithSignature(pcSignature) ELSE oInterface.initWithClass(pcName) ENDIF THIS.interfaceCount = THIS.interfaceCount + 1 DIMEN THIS.Interfaces[THIS.interfaceCount] THIS.Interfaces[THIS.interfaceCount] = oInterface ENDPROC * indexOf * Devuelve la posicion de una interfaz dada dentro de la coleccion * de interfaces o CERO si la interfaz no esta declarada. PROCEDURE indexOf(pcInterface) LOCAL i,nIndex nIndex = 0 pcInterface = LOWER(pcInterface) FOR i = 1 TO THIS.interfaceCount IF LOWER(THIS.Interfaces[i].Name) == pcInterface nIndex = i EXIT ENDIF ENDFOR RETURN nIndex ENDPROC * Accesor para la propiedad Interfaces. Esto permite * acceder a la coleccion de interfaces por posicion * o por nombre de la interfaz PROCEDURE Interfaces_Access(nIndex1, nIndex2) IF VARTYPE(nIndex1) = "N" RETURN THIS.Interfaces[nIndex1] ENDIF LOCAL nIndex nIndex = THIS.indexOf(nIndex1) IF nIndex > 0 RETURN THIS.Interfaces[nIndex] ENDIF ENDPROC * Implements * Permite determinar si una instancia dada implementa * una interfaz especifica PROCEDURE Implements(poObjectRef, pcInterface) LOCAL nIndex,oInterface nIndex = THIS.IndexOf(pcInterface) IF nIndex = 0 RETURN .F. ENDIF LOCAL lResult,i,cMember,cType,cMemberType,lIsMember oInterface = THIS.Interfaces[nIndex] lResult = .T. FOR i = 1 TO oInterface.memberCount cMember = oInterface.Members[i, 1] cType = oInterface.Members[i, 2] IF THIS.getMemberType(poObjectRef, cMember) <> cType lResult = .F. EXIT ENDIF ENDFOR RETURN lResult ENDPROC * getMemberType HIDDEN PROCEDURE getMemberType(poObjectRef, pcMember) DO CASE CASE NOT PEMSTATUS(poObjectRef, pcMember, 5) RETURN "None" CASE TYPE("poObjectRef." + pcMember) = "U" RETURN "Method" OTHERWISE RETURN "Property" ENDCASE ENDPROC * ENDDEFINE * interfaceInfo (Class) * Clase que representa los datos de una interfaz declarada * DEFINE CLASS interfaceInfo AS Custom Signature = "" DIMEN Members[1,1] memberCount = 0 * initWithSignature * Permite inicializar la interfaz con un string de firma que * contenga las propiedades y metodos que componen la interfaz PROCEDURE initWithSignature(pcSignature) LOCAL ARRAY laMembers[1] LOCAL nCount, i, cMember nCount = ALINES(laMembers, STRT(pcSignature,",",CHR(13)+CHR(10))) THIS.memberCount = nCount DIMEN THIS.Members[MAX(1, THIS.memberCount),2] FOR i = 1 TO nCount cMember = laMembers[i] IF LEFT(cMember,1) = "@" THIS.Members[i,1] = SUBS(cMember, 2) THIS.Members[i,2] = "Method" ELSE THIS.Members[i,1] = cMember THIS.Members[i,2] = "Property" ENDIF ENDFOR THIS.Signature = pcSignature ENDPROC #IF "09.00" $ VERSION() * initWithClass * Permite definir el contenido de la interfaz a partir * de una clase dada PROCEDURE initWithClass(pcClassName) LOCAL oInstance oInstance = CREATEOBJECT(pcClassName) THIS.memberCount = AMEMBERS(THIS.Members, oInstance, 1, "U") ENDPROC #ENDIF ENDDEFINE * Interface (Class) * Clase base para la declaracion de interfaces en VFP 9 * DEFINE CLASS Interface AS Custom ENDDEFINE