Modo administración

Forastero en Tierra Extraña

Artículo: Usando las características orientadas a objeto de LotusScript (I) »

JUAN F. RUIZ F. - OCT 4, 2006 (02:15:08 PM)

Un artículo que me resultó interesante y que habla sobre una característica de LotusScript que muchos programadores Notes/Domino descuidamos ( un servidor incluido ): la Programación Orientada a Objetos ( OOP).

El artículo original, cuyo título en inglés es Using the object-oriented features of LotusScript puede ser encontrado en Lotus Developer Domain , concretamente en la sección Iris Today y fue escrito por Bruce Perry.

El ejemplo está incluido con el articulo en mi base de datos de Ejemplos

Índice

Introducción

El desarrollo orientado a objetos (POO) no tiene que ser misterioso. Es realmente nada más que otra forma de organizar tu código. Las buenas noticias es que si estas escribiendo aplicaciones Notes/Domino en LotusScript, ya sabes como usar objetos porque las clases front-end y back-end como NotesUiDocument y NotesDatabase son fundamentales en todo lo que haces. Sin embargo, eres como la mayoría de los desarrolladores de Notes/Domino que conozco, escribes tu código para usar esos objetos en las subrutinas predefinidas ( como QueryOpen y QuerySave ) que Domino Designer crea para tus elementos de diseño, y además escribes tus propias subrutinas y funciones. Eso parece ser típico, incluso entre los más avanzados desarrolladores de LotusScript, pero hay un montón de cosas que puedes hacer con la Orientación a Objetos para mejorar tus aplicaciones.

¿Has visto una base de datos de Notes con cinco o seis versiones de la misma función en diferentes sitios? O peor, ¿Has arreglado un problema en una función de LotusScript solo par descubrir que necesitas arreglarla en el resto de los sitios, tambien? Si es así, tu ya conocerás una ventaja de definir tus propias clases. Las clases te permiten mantener código y datos relacionados de forma conjunta - Una estrategia que a veces es llamada "encapsulación" - de forma que muchos cambios puedan ser echos a tu implementación sin forzarte a zambullirte a través de tu codigo para encontrar todos los distintos sitios que necesitan ser actualizados.

No es del todo sorprendente que las características orientadas a objetos de LotusScript no sean usadas más a menudo de lo que pudieran. Muchos libros y artículos de revistas tienden a tocar ligeramente este tópico. Entonces tambien, algo debe preocupar la necesidad de aprender montones de teoría de Programación Orientada a Objetos. Sin embargo, puedes obtener beneficios prácticos e inmediatos de las clases y los objetos sin tener todos esos problemas. Otros desarrolladores pueden apartarse de ello porque han oído ( como yo lo oí, hace tiempo ) que LotusScript no está orientado a objetos en la forma correcta. LotusScript puede no tener todas las características de Orientación a Objetos de C++ o Java, pero las características que hay pueden ser muy útiles si sabes como tomar ventaja de ellas.

En este artículo, desarrollaremos cinco clases útiles que demuestran las técnicas de Orientación a Objetos. Cada nueva clase se construirá sobre las clases previas para añadir capacidades adicionales. Aprenderemos como extender las clases existentes y los tipos de datos por composición y por herencia. Dado que no es posible heredar de las clases predefinidas de Notes en LotusScript, aprenderemos como usar la composición para saltarnos esa limitación y construir nuevas clases basadas en las clases predefinidas. Tambien aprenderemos sobre las clases base y las subclases y cómo una subclase puede alterar el funcionamiento de las clases desde las que hereda. Tambien veremos el uso de clases que pueden simplificar una base de datos de Notes ayudándonos a eliminar código duplicado. Finalmente, aprenderemos como alterar eventos en nuestras clases y así aprender una forma adicional de compartir código.

Aunque el cubrir cada detalle de las capacidades orientadas a objeto de LotusScript están más allá del alcance de un artículo, veremos las más importantes y recomendaremos recursos para aquellos que quieran saber más. Voy a evitar hablar mucho sobre las características de otros lenguajes orientados a objeto. Aunque ese conocimiento puede darte un entendimiento teórico más amplio de los lenguajes orientados a objeto, no te ayudará a escribir LotusScript orientado a objeto mañana. Para aquellos interesados en información general sobre la programación orientada a objetos, he indicado varios libros en la sección de Recursos Adicionales.

Este articulo está orientado para desarrolladores de Notes que usan LotusScript pero que no han sido expuestos a la programación orientada a objetos aún. El código para este artículo fuen desarrollado y comprobado en una R5 de Notes. Ha tenido alguna comprobación en la versión 4.6 de Notes también. Una base de datos de ejemplo en Iris Sandbox incluye el código descrito en este artículo. Para obtener el máximo de este articulo, encontrarás util el saber sobre librerías de scripts y eventos.

Definiciones básicas

Antes de empezar, miremos unas pocas definiciones. Un objeto es algo con un nombre, un conjunto de atributos, y un conjunto de métodos. Los atributos son valores de datos, y los métodos son piezas de código que pueden manipular los valores de datos. Una clase es un grupo de dimensiones, subrutinas y funciones que pueden representar y manipular los atributos de un objeto. Los dimensionamientos configuran el almacenaje de los atributos del objeto, y las subrutinas y funciones contienen el código de los métodos del objeto. Las clases tambien pueden contener un tipo especial de rutina, llamada propiedad, que es utilizada para obtener o fijar un dato que fue "dimensionado" usando la palabra clave Private.

En LotusScript, un objeto es creado usando la palabra clave New. Puedes tener muchos objetos de la misma clase y al mismo tiempo en tu código, y no tienen que compartir sus valores de datos. La palabra instancia es a menudo usada en vez ( o además de ) objeto en orden a enfatizar este hecho.

Los datos, propiedades y métodos, y métodos de un objeto pueden sólo ser referenciados a traves de la "notación punto" usando la instancia a la izquierda del punto, y el dato, propiedad, o método a la derecha. Por ejemplo, si tienes una clase llamada Book con una propiedad llamada Title, puedes tener un código como este :

Dim ThingOne as New Book
Dim ThinbTwo as New Book
ThingOne.Title = "The Cat In The Hat"
ThingTwo.Title = "Green Eggs And Ham"

A propósito, un avispado programador una vez observó que la programación orientada a objetos sonaba un poco menos solemne y complicada si tu sustituías la palabra cosa por la palabra objeto dondequiera que la vieses. Ahora, echemos una mirada a algo de código orientado a cosas.

Creando una clase simple en LotusScript

Primero echaremos una mirada a una clase simple en LotusScript. En si misma, no es muy útil. Es simplemente un contenedor para una cadena y un valor clave para identificarse. Por favor, confia en mi. En conjunción con algunas otras clases que nosotros vamos a construir, pienso que verás que será un bloque construido muy manejable.

Nota que los datos de la clase están declarados como privados, y el acceso se realiza a través de propiedades en vez del acceso directo a los elementos de datos. ¿Porqué hacemos este paso extraño? Lo hacemos porque el código inevitablemente cambiará. Con estas propiedades en lugar, podemos validar los valores que los usuarios nos dan a traves de las propiedades Set y calcular los valores devueltos por las propiedades Get que no tienen correspondencia con los elementos de datos.

Por ejemplo, la clase podría tener una propiedad adicional que devolviera una dimensión métrica incluso aunque el dato del elemento interno esté almacenado en el sistema inglés. Además, al utilizar una propiedad Set para cada dato, puedes protegerla de ser alterada por cualquier usuario de esa clase debido a que el dato en cuestión es privado. No obtendremos esas ventajas si permitimos el acceso directo a los datos de una clase. En el mundo de la Orientación a Objetos, el proveer acceso a los datos de esta forma se llama Ocultación de datos. Aunque es posible declarar elementos de datos en una clase como publicos; en general , los elementos de datos de una clase deberían ser privados. Los usuarios de la clase deberán estar provistos de propiedades, funciones y subrutinas para acceder sólo a la información que ellos necesitan. Esto les prevendrá de cambiar los datos de una clase de formas no apropiadas.

Public Class ListItem
   ´ Note : this class issues an E_BLANK_KEY error if sub new or set key
´ is passed a blank key
   Private m_key As String
   Private m_value As String

   Sub new (key  As String, value As String)
      If key = "" Then ´ don't allow a blank key
         Error E_BLANK_KEY , E_BLANK_KEY_MSG
      End If
      m_key = key
      m_value = value
   End Sub
   Property Get key As String
      Key = m_key
   End Property
   Property Set key As String
      If key = "" Then ´ don't allow a blank key
         Error E_BLANK_KEY , E_BLANK_KEY_MSG
      End If
   m_key = key
   End Property
   Property Get value As String
      value = m_value
   End Property
   Property Set value As String
      m_value = value
   End Property
   Sub PrintItem
      Print "ListItem: key = " & Me.m_key & " value = " &
      Me.m_value
   End Sub
End Class

Si miras el método New y la propiedad Set Key, verás que ambas emiten un error cuando encuentran una clave en blanco. ¿Porqué es eso? Simplemente porque esa es la mejor manera disponible para indicar que ha ocurrido un problema. Ni la propiedad ni el método tienen un valor de retorno disponible para señalizar condiciones de error.

Creando una clase usando la composición

¿Qué podemos hacer con la clase ListItem? No mucho sin más clases. Creamos otra clase para que podamos hacer algo util. El tipo de datos list de LotusScript es una herramienta extremadamente util. Si has hecho mucho uso de ella, habrás notado que no hay forma de obtener una cuenta de los elementos en una lista sin llevar la cuenta tu mismo ( o recorrer la lista ). Aquí tenemos una clase que puede usarse como un "interfaz" sobre una lista, que solventa ese problema. La clase BetterList BarraLateral contiene una versión no interrumpida del código ).

Public Class BetterList
Private m_list List As Variant
Private m_count As Integer
Property Get Count As Integer
Count = m_count
End Property
Public Function DeleteList
Erase m_list
End Function
Public Sub new
m_count = 0
End Sub
Sub Delete
Call Me.DeleteList
End Sub

Nota que las variables de clase empiezan todas con la cadena m_. Esto es una convención que copié del mundo C++. Nos permite saber de un vistazo qué variables pertenecen a la clase de las que son de la función actual. He encontrado que esta convención nos puede ahorrar un montón de tiempo, especialmente en clases grandes. Nota tambien que m_list es privada; no hay forma de que alguien usando esta clase trastee con la lista directamente. Ese es el único camino para estar absolutamente seguro que el contador permanecerá correcto.

A continuación están las distintas funciones de la clase BetterList ( o métodos ). Son como las funciones regulares, pero están definidas dentro de la clase.

Public Function DeleteItem( key As String) As Integer
Dim rval As Integer
' if the key is in the list, erase the object
If ( Iselement(m_list(key)) ) Then
Erase m_list(key)
m_count = m_count - 1
rval = True
Else
' if there's no such key, warn of an error
rval = False
Print "Item " & key & " not found. It could not be deleted."
End If
DeleteItem = rval
End Function
Public Function AddItem(key As String, item As ListItem) As Integer
' just add the item if the key doesn't exist
If ( Not Iselement(m_list(key)) ) Then
Set m_list(key) = item
m_count = m_count + 1
Else
' if the key does exist, erase the current object in the list
' and add the new one
Erase m_list(key)
Set m_list(key) = item
End If
End Function

En la siguiente sección de codigo, verás que GetItem devuelve un variant. ¿Porqué es asi? Bien, la función podría retornar un ListItem, pero entonces tendriamos que tener clases adicionales como BetterList que devolvieran cada tipo de objeto con el que nosotros quisieramos tratar. De esta forma una clase puede manejar tipos múltiples de objetos. Usando variants de esta forma podemos causar problemas si lo hacemos sin cuidado. Si GetItem devuelve un elemento del tipo equivocado, obtendremos un error en tiempo de ejecución cuando intentemos tratarlo como el tipo que esperamos.

En la siguiente clase ,EnhancedUIDoc, vemos una forma de evitar este problema. Alli, inmediatamente asignamos la variable variant a un objeto del tipo correcto y cualquier otro posterior acceso es hecho via el objeto. Esta asignación en EnhancedUIDoc tambien tiene el efecto de revelar errores en los nombres de propiedad y funciones en tiempo de compilación en vez de en tiempo de ejecución. Prefiero usar el compilador para encontrar errores siempre que pueda. Es bastante más facil que encontrar problemas a traves del testeo y ahorra tiempo tambien.

Esta es la función más importante en esta clase. Es utilizada para tener acceso a los objetos que estamos guardando en la lista.

Tambien, justo debajo, puedes ver que hemos puesto código que maneje errores. Este indica el tipo de error y la clase y método ( o propiedad ) donde ha ocurrido el error; Esto entonces permite al proceso continuar. Aunque básico, esto puede ayudar bastante en la búsqueda del origen de un error. Sin ella, tendremos que hacer un montón de tediosa depuración antes incluso de que sepamos en que parte del codigo el problema ha ocurrido. Si tu aplicación es compleja y haces uso de clases de forma extensiva, encontrarás que esta práctica te puede ahorrar un montón de tiempo. Tambien, puedes querer el considerar crear una base de datos específicamente para grabar los errores.

Public Function GetItem(key As String) As Variant
Dim itm As ListItem
On Error ErrListItemDoesNotExist Goto NoSuchItem
Set GetItem = m_list(key)
Ok:
Exit Function
NoSuchItem:
'return a value of Nothing if the key was not found
Print "List item " & key & " not found."
Set itm = Nothing
Set GetItem = itm
Resume OK
End Function
'see if there's an object in the list for a given key
Public Function IsInList(key As String) As Integer
Dim rval As Integer
If ( Iselement(m_list(key)) ) Then
rval = True
Else
rval = False
End If
IsInList = rval
End Function
End Class

Las funciones y subrutinas en una clase ( por ejemplo, GetItem o AddItem como están usadas aquí ) son frecuentemente llamadas métodos en el mundo de la orientación a objetos. La subrutina Sub New en una clase tiene un nombre especial; es llamada el constructor. Es llamada automáticamente cuando un objeto de esa clase se crea. Esta subrutina es usada comunmente para hacer cualquier inicialización y trabajo de configuración necesitado por la clase.

Es tambien posible tener otra subrutina especial, la Sub Delete. Es conocida como el destructor. Se llama automáticamente cuando un objeto de esa clase es borrado. Ninguna de estas subrutinas especiales es requerida. El constructor es tan útil que raramente no se declara. No trataremos con un destructor a menos que existan características de gestión de memoria con las que tratar.

Discutiremos la gestión de memoria en la sección Objetos y la gestión de memoria de este artículo.

La teoría de la composición

Por cierto, el término técnico para construir una clase de esta forma es composición - tambien conocido como agregación. En este caso, hemos construido una clase nueva que contiene varios tipos de datos definidos previamente. Las clases pueden tambien contener datos que son objetos en sí mismos. Veremos cómo funciona eso en breve.

En el mundo de la orientación a objetos, la composición o agregación es llamada relación "Tiene un". Un coche tiene un motor y ruedas, la fruta tiene semillas y piel, y así. Muchos libros que explican lenguajes orientados a objetos usan diagramas y código basados en ejemplos como ese. Estos diagramas ayudan ciertamente a ilustrar los conceptos de la POO y lo de abajo es mi propia versión, aunque no estoy tan seguro sobre el código que tradicionalmente va con los diagramas. Funcionan, pero no hacen nada util. Pienso que el código incluido en este artículo es un argumento mejor para usar las técnicas de Orientación a Objetos. Muestran como hacer algo util de una manera orientada a objetos.

Construyendo clases por composición

Crear una clase extendiendo una clase predefinida

La clase BetterList es util tal cual; hace cualquier cosa que una lista estandar de Notes puede hacer. No vamos a parar aquí. Usando las dos clases que acabamos de construir, construiremos una nueva clase que puede ser usada para añadir un conjunto de características comunes a todos los formularios de una base de datos. Incluso seremos capaces de cambiar esas características donde las estándares no se deseen. Ya que no es posible crear nuevas clases que hereden de las clases predefinidas de Notes, vamos a extender las capacidades de la clase de front-end NotesUIDocument usando la técnica de la composición de nuevo. Llamaremos a la nueva clase EnhancedUIDoc.

Uno de los mayores beneficios que obtendremos de esta clase será la capacidad de detectar los cambios realizados a cualquier campo en el formulario. Las aplicaciones de Notes hacen esto frecuentemente tomando un campo invisible "sombra" para cada campo que se le sigue la pista. Al usar EnhancedUIDoc, nosotros podemos eliminar la necesidad de añadir campos extra, y de esta forma ayudamos a mantener los formularios simples y ahorrar tiempo de desarrollo. Si no quieres llevar la pista de los cambios de todos los campos, puedes crear una clase que herede de EnhancedUIDoc que mantenga su propia lista de campos a comprobar. Un documento de perfil podría ser un lugar potencial desde el cual cargar una lista.

( Veremos cómo trabaja la herencia en breve; de hecho, usaremos EnhancedUIDoc más tarde como clase base en nuestros ejemplos de herencia ).

Por el hecho de simplificar, esta clase asume que todos los valores de los campos serán cadenas y que habrá solo un valor por campo. Sería fácil cambiar esta clase para que tratara con diferentes tipos de campo y valores múltiples. Los tipos de campo pueden ser determinados a traves de la propiedad Type de NotesItem. La propiedad NotesItem.Values devuelve una matriz que puede ser contada para determinar si contiene un valor único o múltiples valores.

Aquí está la clase EnhancedUIDoc, la cual contiene a la clase BetterList. ( Para una versión no interrumpida del código, vea la clase EnhancedUIDoc de la barra lateral ).

Class EnhancedUIDoc
Private m_uidoc As NotesUIDocument
Private m_uiw As NotesUIWorkspace
Private m_origvalues As BetterList
Private m_doctype As String
Sub ProcessPostOpen( Source As NotesUIDocument )
Dim doc As NotesDocument
Dim Itm As ListItem
Print("EnhancedUIDoc - ProcessPostopen")
Set doc = m_uidoc.document
Forall i In doc.Items
Set Itm = New ListItem( i.Name, i.Values(0) )
Call m_origvalues.AddItem(i.Name, Itm)
End Forall
End Sub
Sub ProcessQuerySave( Source As Notesuidocument, Continue As Variant )
Dim doc A NotesDocument
Dim Itm A ListItem
Dim rval A Integer
Dim v As Variant
Print( " EnhancedUIDoc - ProcessQuerysave")
rval = continue
Set doc = m_uidoc.document
Forall i In doc.Items
Set v = m_origvalues.GetItem(i.Name)

Aquí es donde nosotros devolvemos el variant al objeto. Debemos asegurarnos de que no es nulo primero. De otra forma puede que no sea un objeto real.

If (Not Isnull(v) ) Then   'make sure there's an item to compare it to
Set Itm = v
If i.Values(0) <> Itm.value Then
Print "Item " & i.Name & " new value = " & i.Values(0)
rval = True
Else
Print "Item " & i.Name & " not changed."
End If
Else
Print "Item " & i.Name & " not found."
End If
End Forall
Continue = rval
End Sub
Sub new( uid As NotesUIDocument )
Print ("EnhancedUIDoc - sub new")
Set m_uiw = g_wks
Set m_origvalues = New BetterList
Set m_uidoc = uid

En las siguientes lineas, echa una mirada a las sentencias On Event. ¿Que va a ir aqui? Reemplazamos algunas subrutinas genéricas manejadoras de eventos con unas nuevas definidas dentro de nuestra clase. Esta es una manera más en la que podemos consolidar nuestro código cuando usamos clases. El código en ProcessQuerysave y ProcessPostopen podría haber sido puesto en las subrutinas Querysave y Postopen del formulario, pero eso significaría separar el código para estos eventos del código del resto de la clase.

On Event Querysave From m_uidoc Call ProcessQuerysave
On Event Postopen From m_uidoc Call ProcessPostopen
End Sub
Sub Postopen( Source As Notesuidocument)
End Sub
Sub Querysave(Source As Notesuidocument, Continue As Variant)
End Sub
End Class

Aunque esta clase se creó usando la composición, tambien forma la base para nuestro ejemplo de herencia porque las dos siguientes clases que crearemos heredarán de ella.

La teoría de la herencia

Antes de tratar en las complejidades de la herencia, echemos un vistazo breve a la teoría primero. En los libros de Programación Orientada a Objeto, la herencia es a menudo llamada una relación "es un". Un coche es un tipo de vehículo, un Porsche es un tipo de coche, y así. Plátanos, manzanas, y fresas son todas tipos de frutas. Aqui tenemos otro diagrama tradicional de Orientación a Objetos.

Construyendo clases por herencia

Las subclases son clases que heredan de otra clase. Las subclases se conocen tambien como clases derivadas o clases hijas. Una subclase puede heredar de otra subclase. En el diagrama de arriba, el todoterreno y la furgoneta son ambas subclases que heredan de otra subclase. Las clases base son simplemente esas clases que no heredan de otras clases. ListITem, BetterList, y EnhancedUIDoc son todas clases base. No importa que BetterList y EnhancedUIDoc hagan uso de la composición y así contengan otras clases. Nunca heredan de la otra clase y eso las hace clases base. En el diagrama, vehículo es la unica clase base.

Si tu eres como yo, probablemente te estés calentando la cabeza en este punto y diciendo, "Bien, esta cosa de la herencia suena impresionante, pero ¿qué puedo hacer yo con ella?" Con tu código organizado en clases, puedes facilmente crear nuevas clases basadas en las existentes. El único código que necesitarás escribir para la nueva clase es el que añade la capacidad que no soporta la clase padre. Harás el desarrollo futuro más rápido y fácil. Además, eliminarás situaciones donde el codigo pueda estar duplicado. Ahora veamos como poner la teoría en práctica.

Este artículo fue publicado originalmente en LBH el 10/01/2005 a las 15:34.
Comentarios deshabilitados