Programación orientada a objetos

Cuando comencé a explorar la programación orientada a objetos (POO u OOP, en inglés), allá por el año 1994, me ocurrió lo que a muchos. Luego de casi 10 años de programar proceduralmente (en Basic, Pascal y Cobol) me encontraba con una nueva visión. Si bien creía entender los conceptos de clase, objeto, herencia y polimorfismo, no lograba ver claramente la diferencia de enfoque a la hora de diseñar un programa. No podía apreciar realmente cuáles eran las diferencias y las similitudes entre el enfoque procedural y el orientado a objetos.

A través de mi experiencia docente varias veces me vi frente a la tarea de introducir los conceptos básicos de la POO, encontrándome con la misma situación, pero desde un lugar diferente: ¿Cómo explicar el nuevo enfoque a personas acostumbradas a programar de forma procedural? ¿Cómo resaltar las similitudes y las diferencias? Fue gracias a un ejemplo del libro «C++ Annotations» que encontré una forma simple de presentar la idea básica detrás de la programación orientada a objetos.

Una consideración más que necesaria: este artículo no se trata de un análisis profundo de la programación orientada a objetos, sino simplemente de una simple y breve introducción, intentando dar un panorama inicial a aquellos que no han tenido contacto con (o nunca han entendido) la POO.

Aclaraciones previas

Antes de desarrollar el ejemplo, aclararemos algunos conceptos que muchas veces se prestan a confusión:

  • Lenguajes imperativos: Son aquellos basados en sentencias, ya sean procedurales, orientados a objetos puros o mixtos. Entre ellos se cuentan Pascal, C, C++, Java, Fortran, Perl y Python.
  • Lenguajes procedurales: Son lenguajes imperativos basados en procedimientos (o rutinas) y funciones. Entre ellos podemos nombrar a C, Fortran, Pascal (estándar) y Basic.
  • Lenguajes orientados a objetos: Son lenguajes imperativos basados en clases (algunos, llamados mixtos soportan también el modelo procedural). Entre los lenguajes orientados a objetos puros podemos nombrar a Smalltalk, Eiffel y Java. Entre los mixtos se encuentran C++ y Python.
  • Lenguajes funcionales: Son aquellos basados en funciones matemáticas (y no en comandos o sentencias). Podemos nombrar aquí a ML, Haskell y Lisp.

Un programa procedural simple

Vamos a plantear un ejemplo sencillo. Para ello supondremos un lenguaje imperativo con una sintaxis similar a la de Pascal (usaremos este lenguaje ficticio para simplificar la explicación, en los apéndices al final del artículo podrá encontrar ejemplos equivalentes en PHP, Python y Eiffel).

Supongamos que tenemos el siguiente programa:


program personas;

type persona = record
        nombre: string;
        apellido: string;
        edad: integer
    end;

procedure inicializar(n, a: string; e: integer; var p: persona);
begin
    p.nombre := n;
    p.apellido := a;
    p.edad := e
end;

function es_mayor(p: persona): boolean;
begin
    return p.edad >= 18
end;

function nombre_completo(p: persona): string;
begin
    return p.nombre + " " + p.apellido;
end;

var
    p: persona;

begin
    inicializar("Juan", "Perez", 25, p);
    write(nombre_completo(p));
    if (es_mayor(p)) then
        writeln (" es mayor de edad.")
    else
        writeln (" es menor de edad.")
end.

El programa es bastante sencillo. Primero declaramos un tipo persona que es un registro que contiene los campos nombre, apellido y edad. Luego definimos el procedimiento inicializar que toma el nombre, el apellido, la edad y la persona y asigna los primeros a los campos correspondientes de la última. Luego, un par de funciones (es_mayor y nombre_completo) toman una persona y realizan cálculos sobre los valores de sus campos.

En los programas procedurales hacemos esto todo el tiempo: definimos estructuras y tipos de datos y luego creamos procedimientos y funciones que toman como parámetros variables de estos tipos y realizan distintas operaciones sobre ellos. Dicho de otra manera: podemos ver a los programas procedurales como un conjunto de procedimientos y funciones que manipulan estructuras de datos pasadas como parámetros.

Una visión diferente

Podríamos intentar una visión distinta de la situación. Supongamos que nuestro lenguaje nos permite definir acciones y funciones dentro de un registro. Supongamos además que dichas funciones pueden acceder a todos los campos del registro. De esta forma, por ejemplo, el procedimiento inicializar no necesitaría del parámetro p (la persona a inicializar), sino que actuaría sobre la persona a la que pertenece (ya que se encuentra definido dentro del registro.

De esta forma, podríamos reescribir el programa como sigue:


program personas;

type persona = record
    nombre: string;
    apellido: string;
    edad: integer;

    procedure inicializar(n, a: string; e: integer);
    begin
        nombre := n;
        apellido := a;
        edad := e
    end;

    function es_mayor: boolean;
    begin
        return edad >= 18
    end;

    function nombre_completo: string;
    begin
        return nombre + " " + apellido;
    end

end;

var
    p: persona;

begin
    p.inicializar("Juan", "Perez", 25);
    write(p.nombre_completo);
    if (p.es_mayor) then
        writeln (" es mayor de edad.")
    else
        writeln (" es menor de edad.")
end.

Esta visión alternativa tiene varias ventajas. Inmediatamente notamos que los perfiles de los procedimientos y funciones se simplifican, debido a la eliminación del parámetro que representa a la persona sobre/con la cual se realizarán las operaciones.

Una diferencia importante de notación a la hora de usar la estructura de datos definida, es que ya no utilizamos expresiones de la forma funcion(variable), sino que ahora escribimos variable.funcion.

Una ventaja adicional es que la notación es más consistente. Al referirnos a p.nombre_completo no podemos saber (porque en realidad no interesa) si nombre_completo es un campo del registro o una función. Esto es realmente importante: en el caso del campo edad este podría ser reemplazado por una función que calcule la edad de la persona, añadiendo un campo que represente la fecha de nacimiento. De ocurrir esto, los programas que usen el tipo persona no requerirían mayores modificaciones, ya que podrían seguir haciendo referencia a p.edad. Esto facilita la independencia de la implementación y el encapsulamiento, uno de los conceptos claves de la POO.

Ajustando nuestro vocabulario

A lo que antes llamábamos tipo, refiriéndonos a estructuras de datos, ahora lo llamamos clase, entendiendo como tal no sólo las estructuras, sino también el comportamiento asociado (las acciones y funciones asociadas directamente con la estructura de datos).

A lo que antes llamábamos variable ahora lo llamamos objeto. Así como las variables son de determinado tipo, los objetos son de determinada clase.

Los procedimientos y funciones definidos dentro de una clase se llaman métodos, en tanto que los campos se denominan atributos. En nuestro ejemplo, podríamos decir que la clase persona tiene los atributos nombre, apellido y edad, y los métodos inicializar, nombre_completo y es_mayor.

Un concepto importante: la herencia

En la POO existe la posibilidad de extender el comportamiento de una clase, añadiendo atributos y métodos. El mecanismo utilizado para tal fin se denomina herencia.

Siguiendo con nuestro ejemplo, podríamos querer definir una clase empleado. Básicamente un empleado posee todas las y características de una persona (tiene nombre y apellido, tiene sentido preguntarse si es mayor de edad, etc.). Sin embargo, un empleado tendrá otros atributos (por ejemplo, un salario, un cargo) y también otros métodos (liquidar_salario, etc.). Es por esto que bastará con definir a la clase empleado heredando de la clase persona (en POO se usa la expresión «un empleado es una persona«), y añadiendo el nuevo comportamiento (métodos y atributos).

El secreto: un cambio de visión

La POO no es, como muchos afirman, un paradigma distinto del de la programación procedural. Ambas son dos técnicas distintas para abordar la programación imperativa.

El secreto fundamental consiste en dejar de ver a un programa como un conjunto de acciones y funciones que modifican parámetros, para verlo como un conjunto de objetos con comportamiento, y a estos como estructuras que contienen acciones y funciones.

Si bien la POO involucra una serie de conceptos que escapan al alcance de este texto introductorio, el cambio de visión no es mucho más profundo que el pequeño truco que hemos realizado al incorporar a las funciones dentro de una estructura.

Recomendaciones finales

La POO representa un gran avance en la programación, tal como lo fue la programación estructurada en la década de los ’70. Entre sus ventajas más importante se encuentran un notable aumento de la productividad del programador y de la robustez de los programas.

Personalmente, el lenguaje orientado a objetos que más me agrada es Eiffel (además de sus características de orientación a objetos, soporta la metodología de diseño por contratos). Otro lenguaje muy bien diseñado (y de uso creciente) es Ruby. También es recomendable, y muy simple para comenzar, el lenguaje Python.

Apéndice: Un ejemplo en PHP

He agregado este apéndice siguiendo la sugerencia de un amigo (gracias, Santi) sobre la conveniencia de contar con un ejemplo concreto utilizando el lenguaje PHP, debido a que el mismo es usado por un gran número de personas sin una formación formal en programación.

El ejemplo inicial, usando programación procedural, sería como sigue:


<?
function inicializar($nombre, $apellido, $edad) {
    $persona['nombre'] = $nombre;
    $persona['apellido'] = $apellido;
    $persona['edad'] = $edad;
    return $persona;
}
function nombre_completo($persona) {
    return $persona['nombre'] . ' ' .$persona['apellido'];
}
function es_mayor($persona) {
    return $persona['edad'] >= 18;
}
$p = inicializar('Juan', 'Perez', 25);
echo nombre_completo($p);
if (es_mayor($p)) {
    echo " es mayor de edad.n";
} else {
    echo " es menor de edad.n";
}
?>

Inmediatamente notamos una gran diferencia respecto del ejemplo en Pascal: en PHP nunca definimos un tipo persona ni su estructura. Esto dificulta la comprensión del programa, ya que solamente inspeccionando la función inicializar podemos ver qué campos contiene el arreglo asociativo persona. Todo esto se debe a que PHP es un lenguaje con un sistema de tipos dinámico (una característica que lo hace muy simple y rápido para pequeños desarrollos, pero que se vuelve un arma de doble filo en programas complejos).

La versión orientada a objetos de este mismo programa sería la siguiente:


<?
class Persona {
    var $nombre;
    var $apellido;
    var $edad;
    function Persona($nombre, $apellido, $edad) {
        $this->nombre = $nombre;
        $this->apellido = $apellido;
        $this->edad = $edad;
    }

    function nombre_completo() {
        return $this->nombre . ' ' . $this->apellido;
    }

    function es_mayor($persona) {
        return $this->edad >= 18;
    }
}

$p = new Persona('Juan', 'Perez', 25);
echo $p->nombre_completo();

if ($p->es_mayor) {
    echo " es mayor de edad.n";
} else {
    echo " es menor de edad.n";
}

?>

Este ejemplo nos permite introducir otro concepto de la POO: el uso de constructores. Un constructor es una función de una clase que se ejecuta automáticamente al crear un objeto (instancia). En el caso de PHP (y de varios otros lenguajes) el constructor debe tener el mismo nombre que la clase (en nuestro ejemplo, Persona).

La sentencia $p = new Persona(«Juan», «Perez», 25); crea un nuevo objeto ($p), de la clase Persona, ejecutando la función Persona (que reemplaza a la antigua función inicializar) con los parámetros «Juan», «Perez» y 25.

Otra diferencia respecto del ejemplo anterior, es la utilización del operador -> como selector de métodos y atributos de un objeto. En la definición de la clase Persona, $this es una referencia al objeto actual, necesaria para determinar el alcance de las variables utilizadas (para acceder al atributo nombre de la clase, debemos escribir $this->nombre, ya que $nombre sería interpretado como una nueva variable).

Si usted programa en PHP le recomiendo la utilización de POO (en particular, la versión 5 mejora mucho las características de orientación a objetos del lenguaje).

Apéndice: un ejemplo en Python

A continuación, un ejemplo de la versión orientada a objetos utilizando el lenguaje Python:


class Persona:

    def __init__(self, nombre, apellido, edad):
        self.nombre = nombre
        self.apellido = apellido
        self.edad = edad

    def nombre_completo(self):
        return self.nombre + ' ' + self.apellido

    def es_mayor(self):
        return self.edad >= 18

p = Persona('Juan', 'Perez', 25)

print p.nombre_completo(),

if p.es_mayor:
    print "es mayor de edad."
else:
    print "es menor de edad."

Como podemos apreciar, el constructor de la clase se declara como __init__, en tanto que la referencia al objeto actual se llama self (y debe aparecer como primer parámetro de todos los métodos de la clase, aunque no se utiliza en la invocación de los mismos).

Python es un lenguaje muy simple, pero caben aquí las mismas consideraciones hechas para PHP, respecto de los lenguajes con sistemas de tipos dinámicos.

Apéndice: un ejemplo en Eiffel

Finalmente, un ejemplo en mi lenguaje de programación orientado a objetos favorito: Eiffel. A continuación la definición de la clase persona:


class PERSONA

creation make

feature
    nombre: STRING;
    apellido: STRING;
    edad: INTEGER;

    make(n, a: STRING; e: INTEGER) is
    do
        nombre := n
        apellido := a
        edad := e
    end

    nombre_completo: STRING is
    do
        Result := nombre + " " + apellido
    end

    es_mayor: BOOLEAN is
    do
        Result := edad >= 18
    end
end

Debemos recordar que Eiffel (a diferencia de los demás lenguajes utilizados como ejemplo) es totalmente orientado a objetos (o puro, como suele decirse). Es por esto que no hay diferencia entre las clases y el «programa principal«, siendo este también una clase (llamada raíz o ROOT).

A continuación, el código de la clase ROOT:


class ROOT

creation make

feature

    make is
    local
        p: PERSONA
    do
        create p.make("Juan", "Perez", 25)
        print(p.nombre_completo)
        if p.es_mayor then
            print(" es mayor de edad.%N")
        else
            print(" es menor de edad.%N")
        end
    end
end

Podemos notar que los nombres de clases se escriben con mayúsculas, en tanto que el lenguaje permite especificar cuál es el constructor de cada clase (en este caso, el método make).

Vemos también que sin ver el código de la clase PERSONA es imposible saber si nombre_completo es un atributo (variable) o un método (función). Esto, como habíamos señalado al comienzo del artículo, es una ventaja (notese que en PHP y en Python no ocurre lo mismo), ya que podemos cambiar la implementación de la clase, sin alterar el resto del programa.

Al tener un sistema de tipos fuerte y estático, Eiffel nos obliga a declarar el tipo de cada variable o parámetro utilizado, permitiendo así que el compilador detecte posibles errores antes de la ejecución del programa.

40 comentarios sobre “Programación orientada a objetos

  1. Pingback: meneame.net
  2. Pingback: www.enchilame.com
  3. Dos cositas:

    Correcciones: falta un paréntesis final en el párrafo que le sigue a la definición de la clase PERSONA en Eiffel. Y sobra la primera coma en el párrafo que le sigue: «A, continuación,».

    Pregunta: porqué el método Python «nombre_completo» requiere paréntesis en su invocación y el método «es_mayor» no? Siendo que ambos tienen como parámetro solamente a «self».

  4. Grande Ricardo!!!

    Los errores, corregidos.

    Con respecto a la diferencia entre «nombre_completo» y «es_mayor» en Python, el problema radica en que la invocación al primero ocurre en una sentencia «print». De no colocar los paréntesis, el resultado es algo como lo que sigue:

    bound method Persona.nombre_completo of __main__.Persona instance at 0xa7d40e2c

    He aquí una muestra de las «pequeñas inconsistencias» de Python (por eso, entre otras cosas, no es mi lenguaje favorito).

  5. Buenas
    Interesante, ojala cuelges mas al respecto.
    De momento java me parece que usa menos codigo que los lenguajes que has puesto en los ejemplos.

    Saludos

  6. Me parecio muy interesante el articulo por que diste un pantallaso de los distintos lenguaje.Personalmente solo conocia java ya que es el unico lenguaje que trabaje (con respecto al paradigma POO).Saludos.

  7. Muy buen artículo! Mencionan dos tutoriales de Miguel Ángel Abián, sobre orientación a objetos en javaHispano, que son excelentes. Me han servido para completar ideas que tenía un poco difusas y para aprender sobre lenguajes OO.
    Bravo por ustedes, porque si no jamás los habría descubierto.

    Gracias otra vez y saludos.

  8. necesito saber en que se ralciona microsoft con la oop.

    ademas de que me parecio muy interesante la programacion orientada a objetos en muchos sentidos y deseo que me envien una respuesta acerda de como se relaciona microsoft con la oop.

  9. >Con respecto a la diferencia entre >”nombre_completo” y ”es_mayor” en Python, el >problema radica en que la invocación al primero >ocurre en una sentencia ”print”. De no colocar >los paréntesis, el resultado es algo como lo que >sigue:

    >bound method Persona.nombre_completo of >__main__.Persona instance at 0xa7d40e2c

    >He aquí una muestra de las ”pequeñas >inconsistencias” de Python (por eso, entre otras >cosas, no es mi lenguaje favorito).

    No hay tal inconsistencia (Python, de hecho, se distingue por su consistencia). Tanto ‘nombre_completo’ como ‘es_mayor’ son métodos, así que tienes que llamarlos usando paréntesis, sin ninguna excepción. Y esto es lo que te comentaba Ricardo, que en la línea:

    if p.es_mayor:

    te falta unos paréntesis, para quedar así:

    if p.es_mayor():

    si no, estás evaluando el propio método, lo que siempre resultará verdadero.

    saludos

  10. De verdad que agredezco mucho la explicación, estoy en pleno proceso de transición y me ayudó mucho esta información de verdad que gracias… y espero que nunca pierda la buena motivación de ayudarnos …

  11. Me gustaria q cololocarn tipo documental q indeque como programar desde un inicio de los programas de pascal asta llegar a los mas avansados en este caso q fuera as ta java orientado a objetos

  12. Muy interesante su articulo, pero quiero que hablaran sobre la programacion orientada a objetos y la microsoft, una vision hacia el futuro de la poo y en que se relacion con Borland.

    Gerson

  13. Este articulo es una muy buena forma de explicar la POO, era lo que me faltaba para entender esta tipo de programación, gracias por tomarte la molestia de escribirlo.

  14. Buenas que tal? Soy estudiante de Ingeniera en Informatica y ando buscando material basico como para arrancar con la Programacion Orientada a Objetos de C++.
    Podria ser libros, tutoriales, lo que fuese.
    Muchas gracias.
    Saludos

  15. excelente articulo. Explicado de la mejor manera posible, la verdad como dijo un comentarista… «por fin vi la luz». Con gran simplesa logra hacer entender el paradigma de la POO

  16. Buen articulo, los errores se disculpan, no somos perfectos, podemos equivocarnos, en refrencia al tema, me guztaria saber donde encontrar tutoriales etc… de Eiffel y modelado de objetos sin UML, en castellano, no me guzta leer en ingles.
    desde ya muchas gracias…

  17. He pasado por muchas paginas web, pero ninguna me ha aclarado tanto la POO como esta !!!!
    Lo unico si es posible pediria mas información sobre herecias entre clases y quizas algun ejemplo de como seria el funcionamiento, y tambien si es posible cuando utilizar public private etc….
    Muchisimas gracias.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *