next up previous contents
Next: Mesajes multi-idioma Up: LE-LISP Previous: B.1.3 Tipos de funciones

Programación orientada a objetos con LE-LISP

 

LE-LISP permite definir objetos estructurados del tipo de los record en Pascal, los struct en C o los structure en Fortran. Para ello se usa la primitiva defstruct. Estos objetos estructurados están formados por un cierto número de campos no tipados. Se puede definir una estructura como sub-estructura de una precedente, lo cual permite crear una jerarquía de tipos con herencia. Sin embargo, no se soportan jerarquías múltiples, aunque ello no representa una limitación en la práctica, pues este tipo de herencia plantea numerosos problemas cuando se utiliza. A las estructuras se les pueden asociar métodos, con lo cual se puede conseguir encapsulamiento de funciones y datos, al estilo de los object del Pascal o de las class del C. También se pueden conseguir polimorfismo definiendo métodos con el mismo nombre en objetos distintos para realizar acciones similares. La primitiva send permite enviar un mensaje a un objeto (en el sentido de O.O.gif.). El sistema se ocupa automáticamente de determinar cual es el método adecuado que debe aplicar al objeto para satisfacer el mensaje.

Definición de estructuras

Al definir los objetos estructura, además de definir los campos que lo forman, se puede determinar un valor incial para ellos, que será asignado en ese campo en el momento de creación de cada nueva instancia.

Como ejemplo, a continuación definimos el tipo person como una estructura con los campos name, DNI y birthday. A este último campo se le asigna un valor inicial cada vez que se crea una instancia de person. Dicho valor es el resultado de la evaluación de la función get-date que supongamos devuelve la fecha actual.

(defstruct person
        name
        DNI
        (birthday (get-date)))

Creación de instancias

Para asignar a Manolo una instancia de person, podemos utilizar las dos opciones siguientes:

(setq Manolo (new 'person))
(setq Manolo (#:person:make))

Esto se debe a que cuando se crea una estructura, automáticamente se crea el método make (que devuelve una instancia de la estructura) y un método para acceder a cada campo cuyo nombre coincide con el del campo.

Resumiendo, diremos que defstruct representa la parte declarativa de la programación con tipos estructurados, es decir define las clases, mientras que make y new generan instancias concretas de esas clases (que muchas veces reciben el nombre de objetos en el ámbito de la O.O.)

Métodos

Mediante la sintaxis (#:estructura:campo objeto) podemos recuperar el valor del campo campo en el objeto objeto, que debe ser una instancia de estructura. Si lo que queremos es asignar un valor a un campo, usaremos la sintaxis (#:estructura:campo objeto valor).

Podemos definir un nuevo método llamado age que dado un objeto person devuelva como resultado su edad de la siguiente maneragif:

(defun #:person:age (pers)
       (- (year (get-date)
          (year (#:person:age pers)))

En general, la forma en que se define un método coincide con la forma usual de definir funciones con la salvedad de que el primer parámetro estará ocupado por el objeto receptor del método y que se debe especificar la estructura a la pertenece el método mediante la sintaxis #:estructura:método o una forma abreviada equivalente.

Jerarquías

La utilización de defstruct permite la definición de jerarquías de tipos, es decir, un árbol de tipos en donde las estructuras se corresponden con los nodos y donde los descendientes de un nodo heredan las características de dicho nodo, esto es, sus campos y métodos. El nodo raíz del árbol se denota por #.

Como ejemplo, a continuación se define los tipos lecturer y student que son subtipos de persongif y se crea una instacia para cada uno de ellos:

(defstruct #:person:lecturer
        category)

(defstruct #:person:student
        degree)

(setq Manolo (#:person:lecturer:make))
(setq Pablo (#:person:student:make))

Para obtener la edad de Manolo y Pablo podemos hacer:

(setq manolo-age (#:person:age Manolo))
(setq pablo-age  (#:person:age Pablo))

o bien:

(setq manolo-age (send 'age Manolo))
(setq pablo-age  (send 'age Pablo))

El uso de send es más ineficiente ya que el sistema LE-LISP debe recorrer ascendentemente la jerarquía hasta encontrar el método cuyo nombre coincide con el mensaje, aunque evita al programador el tener que preocuparse del lugar en la jerarquía en que están definidos los métodos. La función send-super es similar a send excepto que comienza a buscar por el método en el nodo padre del objeto pasado como argumento. Otras variantes son send-error, csend y send2.

Por otra parte, al ser posible redefinir los métodos a lo largo de la jerarquía, send aplica siempre el método más cercano ascendentemente al objeto de aplicación, mientras que indicando el nombre completo podemos especificar qué método exacto queremos ejecutar.

Abreviaturas

Para evitar tener que escribir la jerarquía completa como en #:person:lecturer existen las abreviaturas, que se definen usando defabbrev. Por ejemplo, si definimos defabbrev profesor #:person:lecturer, a partir de este momento podremos usar {lecturer} como sustituto de #:person:lecturer en cualquier lugar donde esto último era válido. Las funciones put-abbrev, get-abbrev, abbrevp, has-an-abbrev y rem-abbrev también están relacionadas con las abreviaturas.


next up previous contents
Next: Mesajes multi-idioma Up: LE-LISP Previous: B.1.3 Tipos de funciones

Miguel A. Alonso Pardo
Thu Nov 20 16:47:01 CET 1997