Una de las principales
inquietudes que expresan los estudiantes acerca del paradigma orientado a
objetos está relacionada con los mensajes y los métodos. En este
sentido, se iniciará con un ejemplo sumamente sencillo el cual irá
evolucionando progresivamente con la finalidad de ilustrar dichos
conceptos.
Métodos sin argumentos.
El Ejemplo Parvulo1 muestra la definición de la clase parvulo1, misma
que no contiene atributos pero sí un método cuyo identificador o nombre
es "mensaje" (línea 10).
El método "mensaje" se encuentra definido en las líneas 13-15, y tiene como única responsabilidad la impresión en la salida estándar de una cadena (los detalles generales del funcionamiento de cout << son presentados en la entrada Un vistazo a C++). La definición de un método en C++ tiene en general la siguiente estructura:
El método "mensaje" se encuentra definido en las líneas 13-15, y tiene como única responsabilidad la impresión en la salida estándar de una cadena (los detalles generales del funcionamiento de cout << son presentados en la entrada Un vistazo a C++). La definición de un método en C++ tiene en general la siguiente estructura:
tipo_de_retorno clase : : metodo( );
donde : :
es el operador de resolución de alcance, y esencialmente le indica al
compilador que "metodo" pertenece o está en el alcance de "clase".
Con base en base a lo anterior, la clase parvulo1 es una especie de
plantilla capaz de generar objetos con una única responsabilidad o
servicio. Para poder instanciar objetos el Ejemplo Parvulo1 muestra la
función main en las líneas 17-21, en donde se crea un objeto "parvulo"
de la clase parvulo1 (línea 18) y se le envía el mensaje "mensaje"
(línea 20). En general, esta será la estructura de la mayoría de los
ejemplos que se utilizarán en el blog.
Una vez que el objeto existe, es decir, una vez que el objeto ha sido instanciado, es posible entonces interactuar con él por medio de mensajes para solicitarle acciones que correspondan con las responsabilidades o servicios definidos para dicho objeto que, para el caso del objeto parvulo, es sólo una.
La solicitud del único servicio que puede proporcionar el objeto parvulo se realiza a través del mensaje "mensaje", el cual es enviado (invoca la función miembro correspondiente) al objeto en la línea 20:
Una vez que el objeto existe, es decir, una vez que el objeto ha sido instanciado, es posible entonces interactuar con él por medio de mensajes para solicitarle acciones que correspondan con las responsabilidades o servicios definidos para dicho objeto que, para el caso del objeto parvulo, es sólo una.
La solicitud del único servicio que puede proporcionar el objeto parvulo se realiza a través del mensaje "mensaje", el cual es enviado (invoca la función miembro correspondiente) al objeto en la línea 20:
parvulo.mensaje( );
La expresión anterior se interpreta como: "se envía el mensaje
<<mensaje>> al objeto parvulo". El envío de mensajes no es
otra cosa más que la invocación explícita de un método (función miembro
de la clase) a través de su identificador; es utilizar el método
correspondiente para realizar una acción, misma que está relacionada con
el comportamiento o las responsabilidades definidas en la clase del
objeto en cuestión.
La salida del Ejemplo Parvulo1 se muestra a continuación. Asegúrese de comprender lo descrito hasta aquí antes de continuar.
La salida del Ejemplo Parvulo1 se muestra a continuación. Asegúrese de comprender lo descrito hasta aquí antes de continuar.
Dentro del metodo mensaje()
En esta sección se presenta una versión ligeramente distinta del Ejemplo Parvulo1, en el cual se presentó el envío de mensajes sin
argumentos. Tómese el tiempo necesario para comparar el Ejemplo Parvulo2
de esta sección con el Ejemplo Parvulo1 de la sección anterior, y
compruebe que son esencialmente iguales.
El parámetro "nombre" en el método mensaje constituye la diferencia de los dos ejemplos anteriormente mencionados. En el Ejemplo Parvulo2 el método "mensaje" (línea 10) define ahora la capacidad de recibir un argumento de tipo cadena (un objeto de la clase string) referido por el objeto "nombre". El método "mensaje" imprime en la salida estándar un cadena conformada por un texto predefinido (línea 14), y la concatenación de la cadena referida por "nombre" y "\n".
Por otro lado, la función main es también similar a la del Ejemplo Parvulo1 excepto en la forma en que se envía el mensaje al objeto "parvulo" (línea 21). Observe que el mensaje enviado tiene ahora una cadena como argumento, la cual es referida por el objeto "nombre" del método "mensaje" (línea 13) en el momento en que se le envía el mensaje "mensaje" al objeto "parvulo".
El parámetro "nombre" en el método mensaje constituye la diferencia de los dos ejemplos anteriormente mencionados. En el Ejemplo Parvulo2 el método "mensaje" (línea 10) define ahora la capacidad de recibir un argumento de tipo cadena (un objeto de la clase string) referido por el objeto "nombre". El método "mensaje" imprime en la salida estándar un cadena conformada por un texto predefinido (línea 14), y la concatenación de la cadena referida por "nombre" y "\n".
Por otro lado, la función main es también similar a la del Ejemplo Parvulo1 excepto en la forma en que se envía el mensaje al objeto "parvulo" (línea 21). Observe que el mensaje enviado tiene ahora una cadena como argumento, la cual es referida por el objeto "nombre" del método "mensaje" (línea 13) en el momento en que se le envía el mensaje "mensaje" al objeto "parvulo".
Asegúrese de realizar una labor
analítica al comparar línea a línea tanto las clases parvulo1 y parvulo2
como las respectivas funciones main; así como de comprender sus
diferencias con base en lo descrito hasta ahora.
La salida del Ejemplo Parvulo2 se muestra a continuación:
La salida del Ejemplo Parvulo2 se muestra a continuación:
Mi nombre es Ricardo
Note en las líneas 10 y 13 que el parámetro ha sido declarado como
"const string& nombre". Esto es una conveniencia más que una regla,
dicha declaración le indica al compilador que el parámetro "nombre" es
una referencia constante a una cadena, esto quiere decir que no se
genera una copia de la cadena y que ésta no va a poder ser modificada
dentro del método o función miembro. En general, por eficiencia, esta
será la estructura que se utilice para la mayoría de los métodos de este
tipo.
Métodos y atributos.
Por el
principio de ocultación de información es conveniente que únicamente se
tenga acceso a los atributos de una clase a través de su interfaz. La
interfaz de un objeto está representada por sus métodos públicos
(public).
El Ejemplo Parvulo3 hace uso de dicho principio al definir, con un acceso restringido o privado (private), el atributo nombre (línea 10) para la clase parvulo3. En C++, por definición, todo lo que se defina dentro de una clase es privado a no ser que se especifique lo contrario (línea 12), por lo que es muy común omitir la palabra reservada private. Es posible intercalar los modificadores de acceso (public, protected y private), se utilizará el último definido hasta que se encuentre otro o el fin de la clase.
Observe que la clase del ejemplo en cuestión declara también tres métodos públicos (líneas 13-15), los cuales establecen la interfaz de los objetos que vayan a ser instanciados:
El Ejemplo Parvulo3 hace uso de dicho principio al definir, con un acceso restringido o privado (private), el atributo nombre (línea 10) para la clase parvulo3. En C++, por definición, todo lo que se defina dentro de una clase es privado a no ser que se especifique lo contrario (línea 12), por lo que es muy común omitir la palabra reservada private. Es posible intercalar los modificadores de acceso (public, protected y private), se utilizará el último definido hasta que se encuentre otro o el fin de la clase.
Observe que la clase del ejemplo en cuestión declara también tres métodos públicos (líneas 13-15), los cuales establecen la interfaz de los objetos que vayan a ser instanciados:
- establece_nombre: este tipo de métodos son utilizados comúnmente para ajustar o establecer el valor de un atributo; y al menos en principio, debería haber un método de este tipo por cada atributo que contenga la clase, siempre y cuando se requiera manipular desde el exterior. Este tipo de métodos son comúnmente referidos como métodos de tipo set.
- obten_nombre: este tipo de métodos son utilizados comúnmente para recuperar u obtener el valor de un atributo, y al igual que antes, debería haber, al menos en principio, un método de este tipo por cada atributo que contenga la clase siempre y cuando se requiera visualizar desde el exterior. Este tipo de métodos son comúnmente referidos como métodos de tipo get.
- mensaje: este ha sido descrito con anterioridad. Note que el método está definido como el del Ejemplo Parvulo1 (sin argumentos), pero funciona como el del Ejemplo Parvulo2. Asegúrese de comprender esto antes de continuar.
Observe que el método "mensaje" (líneas 26-29) se vale del
método "obten_nombre" (línea 22) para acceder al atributo "nombre" pero no
necesariamente tiene que ser así, ya que un método puede acceder
directamente a los atributos de la clase siempre y cuando ambos estén
definidos dentro del mismo ámbito de clase (encapsulamiento). Por otro lado, si
el atributo tiene un nivel de acceso protegido (protected), los métodos
de las clases derivadas por herencia también podrían acceder a los
atributos de la clase de la que derivan (clase padre o super clase).
Los métodos de tipo set sólo deben trabajar sobre un atributo, por lo que habitualmente sólo reciben un argumento, mismo que se corresponde con la clase (tipo) del atributo a modificar (string para el caso del Ejemplo Parvulo3). De manera análoga, los métodos de tipo get no reciben ningún tipo de argumento, y la clase de objetos que regresan está directamente relacionada con la clase (tipo) del atributo al que accederán (string para el caso del Ejemplo Parvulo3).
Los métodos de tipo set sólo deben trabajar sobre un atributo, por lo que habitualmente sólo reciben un argumento, mismo que se corresponde con la clase (tipo) del atributo a modificar (string para el caso del Ejemplo Parvulo3). De manera análoga, los métodos de tipo get no reciben ningún tipo de argumento, y la clase de objetos que regresan está directamente relacionada con la clase (tipo) del atributo al que accederán (string para el caso del Ejemplo Parvulo3).
Es importante hacer notar también que la clase parvulo3, a diferencia
de las anteriores, establece ya una característica representada y
definida por el atributo "nombre", de tal forma que los objetos derivados
de ella (párvulos) compartirán dicha característica (un párvulo es un
niño pequeño en edad preescolar), aunque cada uno poseerá su propia
identidad (nombre).
El Ejemplo Parvulo3 muestra también la función main. Al igual que en los ejemplos anteriores, observe que en la línea 32 se define y crea el objeto parvulo.
Las líneas 34 y 36 hacen uso del método obten_nombre a través del envío del mensaje correspondiente, mientras que la línea 35 envía el mensaje establece_nombre con un argumento específico. Finalmente, la línea 37 muestra el envío del mensaje mensaje, el cual debería resultar ya familiar al lector.
La salida del Ejemplo Parvulo3 se muestra a continuación. Observe que inicialmente el atributo nombre del objeto parvulo aparece en blanco.
El Ejemplo Parvulo3 muestra también la función main. Al igual que en los ejemplos anteriores, observe que en la línea 32 se define y crea el objeto parvulo.
Las líneas 34 y 36 hacen uso del método obten_nombre a través del envío del mensaje correspondiente, mientras que la línea 35 envía el mensaje establece_nombre con un argumento específico. Finalmente, la línea 37 muestra el envío del mensaje mensaje, el cual debería resultar ya familiar al lector.
La salida del Ejemplo Parvulo3 se muestra a continuación. Observe que inicialmente el atributo nombre del objeto parvulo aparece en blanco.
Nombre del parvulo:Métodos y constructores.
Nombre del parvulo: Ricardo
Mi nombre es Ricardo
Un constructor es un método especial que se invoca implícitamente
cuando se crea o instancia un objeto. Cuando se crea un objeto, se
genera la memoria necesaria para representarlo y se le inicializa por
medio de un constructor. En este sentido, puede haber más de una forma
de inicializar un objeto y, en consecuencia, más de un constructor.
El identificador o nombre de los métodos constructores debe coincidir con el identificador o nombre de la clase que los define; con base en lo anterior, se tiene que puede haber más de un constructor compartiendo el mismo identificador. El mecanismo que tienen los constructores para distinguirse entre sí, es a través del número y clase o tipo de sus parámetros, constituyendo con ello una forma de polimorfismo comúnmente conocida como sobrecarga (la sobrecarga se trata con un poco más de detalle en la siguiente sección). Durante el proceso de creación del objeto, se invoca el constructor que coincida con el número y tipo de argumentos proporcionados en su definición.
Las líneas 14 y 20-23 del Ejemplo Parvulo4 muestran la declaración y la definición respectivamente del constructor parvulo4; observe cómo el nombre del constructor coincide exactamente con el nombre de la clase. Dicho constructor define un único parámetro n el cual es un objeto de la clase string.
El identificador o nombre de los métodos constructores debe coincidir con el identificador o nombre de la clase que los define; con base en lo anterior, se tiene que puede haber más de un constructor compartiendo el mismo identificador. El mecanismo que tienen los constructores para distinguirse entre sí, es a través del número y clase o tipo de sus parámetros, constituyendo con ello una forma de polimorfismo comúnmente conocida como sobrecarga (la sobrecarga se trata con un poco más de detalle en la siguiente sección). Durante el proceso de creación del objeto, se invoca el constructor que coincida con el número y tipo de argumentos proporcionados en su definición.
Las líneas 14 y 20-23 del Ejemplo Parvulo4 muestran la declaración y la definición respectivamente del constructor parvulo4; observe cómo el nombre del constructor coincide exactamente con el nombre de la clase. Dicho constructor define un único parámetro n el cual es un objeto de la clase string.
La inicialización que hace el constructor parvulo4 consiste únicamente de la asignación del objeto n al atributo
representado por el objeto "nombre" (línea 22). Note que en C++ la forma
de hacer lo anterior dentro de un constructor, es invocar al constructor
correspondiente del parámetro que se desea inicializar (línea 21).
Es importante mencionar que la labor de inicialización de un constructor en particular puede ser un proceso mucho más elaborado que el descrito hasta ahora, y que estará en función directa de las responsabilidades de inicialización con que se quiera dotar al constructor, así como de la problemática en particular que se esté resolviendo por medio del objeto en cuestión.
Los elementos restantes de la clase parvulo4 han sido previamente abordados en las secciones anteriores por lo que no se repetirán aquí.
Es importante mencionar que la labor de inicialización de un constructor en particular puede ser un proceso mucho más elaborado que el descrito hasta ahora, y que estará en función directa de las responsabilidades de inicialización con que se quiera dotar al constructor, así como de la problemática en particular que se esté resolviendo por medio del objeto en cuestión.
Los elementos restantes de la clase parvulo4 han sido previamente abordados en las secciones anteriores por lo que no se repetirán aquí.
El Ejemplo Parvulo4 tiene también la función main (líneas 37-44).
Observe que a diferencia de los ejemplos anteriores, en la línea 38 se
proporciona un argumento al constructor, lo cual hace que desde la
creación del objeto parvulo se le esté definiendo un nombre.
Observe también cómo la secuencia de mensajes subsecuentes coincide con las del Ejemplo Parvulo3, excepto por la línea 41 del Ejemplo Parvulo4, en donde se envía el mensaje establece_nombre al objeto parvulo para asignarle el nombre completo (con apellidos) al párvulo.
Asegúrese de comprender antes de continuar, que lo siguiente muestra la salida correspondiente a la ejecución del Ejemplo Parvulo4:
Observe también cómo la secuencia de mensajes subsecuentes coincide con las del Ejemplo Parvulo3, excepto por la línea 41 del Ejemplo Parvulo4, en donde se envía el mensaje establece_nombre al objeto parvulo para asignarle el nombre completo (con apellidos) al párvulo.
Asegúrese de comprender antes de continuar, que lo siguiente muestra la salida correspondiente a la ejecución del Ejemplo Parvulo4:
Nombre del parvulo: RicardoSobrecarga.
Nombre del parvulo: Ricardo Ruiz Rodriguez
Mi nombre es Ricardo Ruiz Rodriguez
La sobrecarga (overload) es un tipo de polimorfismo que se caracteriza
por la capacidad de poder definir más de un método o constructor con el
mismo nombre (identificador), siendo distinguidos entre sí por el número
y la clase (tipo) de los argumentos que definen.
El Ejemplo Parvulo5 muestra la sobrecarga de constructores. Note que las líneas 13 y 20-22 declaran y definen respectivamente el mismo constructor que el en Ejemplo Parvulo4 excepto por el nombre, y que se ha añadido o sobrecargado un nuevo constructor (líneas 14 y 24-27), el cual recibe tres argumentos que representan el nombre (n), el primer apellido (a1), y el segundo apellido (a2) de un párvulo. Note también la inicialización del atributo "nombre" en la línea 25.
La sobrecarga de constructores se da porque ambos constructores tiene el mismo identificador (parvulo5) pero distinto número de parámetros.
No puede existir sobrecarga para constructores o métodos con el mismo identificador y el mismo número o clase (tipo) de parámetros; tiene que haber algo que los distinga entre sí, ya que en otro caso habría ambigüedad y un buen compilador la detectará.
El Ejemplo Parvulo5 muestra la sobrecarga de constructores. Note que las líneas 13 y 20-22 declaran y definen respectivamente el mismo constructor que el en Ejemplo Parvulo4 excepto por el nombre, y que se ha añadido o sobrecargado un nuevo constructor (líneas 14 y 24-27), el cual recibe tres argumentos que representan el nombre (n), el primer apellido (a1), y el segundo apellido (a2) de un párvulo. Note también la inicialización del atributo "nombre" en la línea 25.
La sobrecarga de constructores se da porque ambos constructores tiene el mismo identificador (parvulo5) pero distinto número de parámetros.
No puede existir sobrecarga para constructores o métodos con el mismo identificador y el mismo número o clase (tipo) de parámetros; tiene que haber algo que los distinga entre sí, ya que en otro caso habría ambigüedad y un buen compilador la detectará.
Los
métodos restantes del Ejemplo Parvulo5 ya ha sido comentados y descritos
con anterioridad en otras secciones de esta misma entrada.
Respecto a la función main, es también muy similar a los anteriormente explicados. Únicamente cabe resaltar la creación del objeto parvulo en la línea 42. Note que ahora se le proporcionan tres argumentos al constructor, lo cual hace que el constructor utilizado sea el definido en las líneas 24-27.
La salida del Ejemplo Parvulo5 se muestra a continuación. Compare dicha salida con la del ejemplo anterior y asegúrese de comprender la diferencia.
Respecto a la función main, es también muy similar a los anteriormente explicados. Únicamente cabe resaltar la creación del objeto parvulo en la línea 42. Note que ahora se le proporcionan tres argumentos al constructor, lo cual hace que el constructor utilizado sea el definido en las líneas 24-27.
La salida del Ejemplo Parvulo5 se muestra a continuación. Compare dicha salida con la del ejemplo anterior y asegúrese de comprender la diferencia.
Nombre del parvulo: Ricardo Ruiz Rodriguez
Nombre del parvulo: Ricardo
Mi nombre es Ricardo
No hay comentarios.:
Publicar un comentario