Saltar al contenido principal
Logo FPCode

Programación orientada a objetos

Contenido

Conceptos fundamentales de la programación orientada a objetos: clases, objetos, atributos, métodos, encapsulación, herencia, polimorfismo y relaciones entre clases.

Introducción

La programación orientada a objetos (POO) es un paradigma de programación que organiza el código alrededor de objetos. Un objeto agrupa datos y comportamientos relacionados, de forma parecida a como pensamos en entidades del mundo real: una cuenta bancaria, un coche, un usuario, un producto o una partida de un videojuego.

Este enfoque ayuda a construir programas más organizados, reutilizables y fáciles de mantener. En lugar de trabajar únicamente con variables y funciones aisladas, la POO propone modelar el problema mediante clases, objetos, atributos, métodos y relaciones.

Conocimiento previo

Referencias

Índice

  1. Introducción a la Programación Orientada a Objetos 1.1. ¿Por qué surge la POO? 1.2. Principios
  2. Componentes de la POO 2.1. Clases y objetos 2.2. Atributos (propiedades) y estados 2.3. Métodos 2.3.1. Métodos de acceso o propiedades 2.3.2. Constructores y destructores
  3. Encapsulación y visibilidad
  4. Relaciones entre clases
  5. Herencia
  6. Polimorfismo
  7. Diagrama de clases
  8. Ejercicios

1. Introducción a la Programación Orientada a Objetos

La Programación Orientada a Objetos (abreviada como POO) es un paradigma de programación que cambia la forma en que pensamos y diseñamos el software.

Mientras que en la programación estructurada el foco está en las funciones y los procedimientos, en la POO el centro son los objetos, es decir, entidades que combinan datos y comportamiento en una sola unidad.

Podemos decir que la POO nos permite modelar problemas del mundo real de forma más intuitiva, representando cosas como usuarios, productos, vehículos, personajes de un videojuego o cuentas bancarias directamente en el código. Cada uno de estos elementos se convierte en un objeto, con sus propios atributos y acciones posibles.

1.1. ¿Por qué surge la POO?

Imagina un programa muy grande desarrollado únicamente con procedimiento, funciones y variables globales. Conforme el sistema crece, se vuelve cada vez más difícil de entender, mantener y ampliar. Los cambios en una parte del código pueden afectar a otras sin que nos demos cuenta, generando errores difíciles de localizar.

En los años 70 y 80, los programadores comenzaron a buscar formas de estructurar el código de manera más modular, para poder dividir un sistema complejo en pequeñas partes independientes y reutilizables. Así es como surge la Programación Orientada a Objetos, que permite construir software basado en unidades más cercanas a cómo pensamos en la vida real: objetos con propiedades y comportamientos propios.

Un poco de historia

El primer lenguaje que implementó conceptos orientados a objetos fue Simula 67, diseñado en los años 60 para simular entornos del mundo real. Más adelante, en los años 80, el lenguaje Smalltalk llevó la POO al siguiente nivel, y poco después se popularizaron otros como C++, Java y Python, que adoptaron estos conceptos y los hicieron accesibles a millones de programadores.

Ventajas principales
  • Organización: el código se agrupa por responsabilidades.
  • Reutilización: Se pueden crear objetos y clases reutilizables en diferentes proyectos.
  • Mantenimiento: Los cambios afectan sólo a los objetos relacionados.
  • Escalabilidad: facilita ampliar programas grandes sin perder estructura.
  • Modularidad: Es más sencillo dividir un sistema en partes independientes.
  • Mejor comprensión del problema: El código refleja la estructura del problema real.

💡 Ejemplo

Si piensas en un videojuego de coches, cada vehículo tiene unas propiedades que son comunes (Marca, Modelo, etc.), ahora bien sus valores cambian. Tal y como hemos aprendido ahora, necesitariamos bastantes variables para representar estas propiedades.

pseudocodigo.psc
Inicio
Definir marcaCoche1, marcaCoche2 como Cadena;
Definir velocidadMaxCoche1, velocidadMaxCoche2 como Real;
marcaCoche1 ← "Toyota";
velocidadMaxCoche1 ← 180;
marcaCoche2 ← "Renault";
velocidadMaxCoche2 ← 160;
Fin

Imagina que hay que hacer esto para 1.000 coches o que queremos incluir nuevas propiedades, como el color, modelo, etc..

Con la POO, cada coche es un objeto, y el código se organiza de una manera más natural:

pseudocodigo.psc
Inicio
Definir coche1, coche2 como Coche;
coche1 ← ("Toyota",180);
coche2 ← ("Renault",160);
Fin

De esta forma, el programa es más ordenado, más fácil de mantener y más fácil de entender.

Por lo tanto, cada objeto tiene:

  • Datos, que describen su estado.
  • Comportamientos, que representan las acciones que puede realizar.

1.2. Principios

La POO se basa en un conjunto de características fundamentales que definen su forma de trabajar.

  1. Encapsulación: proteger los datos de un objeto para que no puedan ser modificados directamente desde fuera.
  2. Abstracción: simplificar la realidad mostrando sólo lo esencial y ocultando los detalles que no son importantes para el usuario.
  3. Herencia: crear un objeto nuevo a partir de otro existente, reutilizando su código y ampliándolo.
  4. Polimorfismo: un mismo método puede comportarse de diferentes formas según el objeto que lo use.

Estos principios pueden ser dificiles de entender por primera vez. Más adelante se explicaran con mayor detalle.

2. Componentes de la POO

En la Programación Orientada a Objetos, el código se organiza a través de componentes principales que permiten construir y estructurar los programas.

Estos componentes son los bloques básicos con los que se modelan las entidades del mundo real dentro del software.

2.1. Clases y objetos

¿Qué es una clase?

Una clase es como un molde o una plantilla que define cómo serán los objetos que se creen a partir de ella.

Dentro de una clase se describen:

  • Los atributos (también llamados propiedades), que representan las características o datos del objeto.
  • Los métodos, que son las acciones o comportamientos que ese objeto puede realizar.

Siguiendo el ejemplo del coche 🚗:

Imagina que quieres modelar un coche en un programa. La clase Coche podría tener:

  • Atributos: marca, modelo, color, velocidad.
  • Métodos: acelerar(), frenar(), girar().
pseudocodigo.psc
Clase Coche
atributos:
marca
modelo
color
velocidad
métodos:
acelerar()
frenar()
girar()
Fin Clase
¿Qué es un objeto?

Un objeto es una instancia concreta de una clase. Es decir, cuando creas un objeto, estás creando un ejemplar real de esa clase.

Ejemplo:

Si tienes la clase Coche, puedes crear varios objetos:

  • miCoche → marca: Toyota, modelo: Corolla
  • tuCoche → marca: Ford, modelo: Focus
pseudocodigo.psc
coche1 = Coche("Toyota", "Corolla")
coche2 = Coche("Renault", "Clio")

Cada objeto tiene sus propios datos, aunque comparten la misma estructura y comportamientos definidos en la clase.

2.2. Atributos (propiedades) y estados

Los atributos son los datos que describen un objeto. También se llaman propiedades o campos, dependiendo del lenguaje de programación. Son las variables que guardan información sobre el estado particular de cada objeto.

En un objeto Coche, algunos atributos podrían ser:

  • marca
  • modelo
  • color
  • velocidad

El conjunto de valores de sus atributos en un momento determinado se conoce como estado del objeto.

pseudocodigo.psc
coche1.marca ← "Toyota"
coche1.modelo ← "Corolla"
coche1.color ← "Rojo"
coche1.velocidad ← 0

Estado de los objetos
Los atributos representan el estado del objeto en un momento dado. Si cambias los valores de los atributos, estás modificando el estado del objeto.

Por ejemplo, si aceleras el coche, el atributo velocidad cambia y por lo tanto cambia su estado.

pseudocodigo.psc
Inicio
miCoche.velocidad = 50
Fin

2.3. Métodos

Los metodos materializan las acciones que van a tener los objetos, es decir, es la forma en que se implementa un algoritmo que se aplica a todas las instancias de una clase, y si se desea, pueden utilizar los datos de los atributos que contiene cada objeto.

Ejemplo:

pseudocodigo.psc
Clase Coche
atributo velocidad
método acelerar(cantidad)
velocidad ← velocidad + cantidad
fin método
método frenar(cantidad)
velocidad ← velocidad - cantidad
fin método
Fin Clase

Un método puede comportarse como:

  • Función. Cuando el algoritmo que contiene, al ser llamado, realiza una serie de instrucciones y por último devuelve un valor.
  • Procedimiento. Cuando el algoritmo que contiene, al ser llamado, lleva a cabo una serie de acciones, pero en el resultado final no se espera que devuelva ningún valor.

Varios son los factores de los que depende que un método se implemente como procedimiento o como función. Primero, depende del lenguaje de programación utilizado, ya que algunos como Java solo utilizan funciones (no existen procedimientos como tal), y otros como Delphi distinguen entre funciones y procedimientos.

Los métodos pueden recibir parámetros o argumentos, que permiten enviar datos al algoritmo para que pueda realizar su funcionalidad. Estos parámetros se comportan como variables que solo sirven dentro del bloque del método.

Los parámetros o argumentos puede ser enviados al método de dos formas:

  • Por valor -> Se crea una copia exacta de la información, una vez utilizada por el método, los datos del parámetro no se verán alterados.
  • Por referencia -> Se envia una dirección de memoria dónde se encuentran los datos del parámetro, cuando se utiliza dentro del método los datos se verán afectados una vez finalizado.

2.3.1. Métodos de acceso o propiedades

Los métodos de acceso son operaciones que permiten consultar o modificar el valor de los atributos de un objeto de forma controlada. En lugar de acceder directamente a los datos internos de una clase, estos métodos actúan como intermediarios, garantizando que cualquier lectura o modificación se realice siguiendo las reglas definidas por la propia clase.

Tradicionalmente, los métodos encargados de obtener el valor de un atributo reciben el nombre de getters, mientras que los que permiten modificarlo se denominan setters.

Gracias a este mecanismo, la clase mantiene el control sobre su estado interno. Por ejemplo, un método setter puede comprobar que un valor sea válido antes de almacenarlo, evitando que el objeto quede en un estado inconsistente.

En algunos lenguajes de programación, como C#, Kotlin o Swift, estos métodos se presentan mediante propiedades. Desde el punto de vista del programador, una propiedad se utiliza como si fuera un atributo, aunque internamente ejecuta código para realizar la lectura o escritura del dato. Esto proporciona una sintaxis más sencilla sin renunciar al control sobre el acceso a la información.

De esta forma, los métodos de acceso y las propiedades permiten que el objeto controle su propio comportamiento. En lugar de modificar sus datos desde cualquier parte del programa, se utilizan operaciones definidas dentro de la propia clase para consultar o actualizar sus atributos.

Getters

Los getters son métodos cuyo objetivo es devolver el valor de un atributo. Normalmente no modifican el estado del objeto, sino que únicamente proporcionan acceso a la información cuando es necesario.

Por ejemplo, un objeto Persona podría disponer de un método getNombre() que devuelva el nombre almacenado en el atributo correspondiente.

pseudocodigo.psc
Clase Persona
atributo nombre
método getNombre()
devolver nombre;
fin método
Fin Clase

Setters

Los setters son métodos que permiten modificar el valor de un atributo. Antes de realizar el cambio, pueden comprobar que el nuevo valor cumpla determinadas condiciones.

Por ejemplo, un método setEdad() podría impedir que se asignen edades negativas o superiores a un determinado límite, garantizando así la coherencia de los datos del objeto.

pseudocodigo.psc
Clase Persona
atributo edad
método setEdad(nuevaEdad)
Si nuevaEdad >= 0 Entonces
edad ← nuevaEdad;
Si no
Escribir "Edad inválida";
Fin si
fin método
Fin Clase

2.3.1. Constructores y destructores

Un constructor es un método especial que se ejecuta cuando se crea un objeto. Su función principal es inicializar los atributos con valores adecuados.

Los métodos constructores se podrán identificar facilmente porque utilizan por defecto el mismo nombre de la clase.

Ejemplo:

pseudocodigo.psc
Clase Coche
atributo marca
atributo modelo
atributo velocidad
constructor Coche(marcaInicial, modeloInicial)
marca ← marcaInicial
modelo ← modeloInicial
velocidad ← 0
fin constructor
Fin Clase

Gracias al constructor, cada objeto empieza con un estado válido desde el momento en que se crea.

Cada clase puede contar con varios métodos constructores definidos dependiendo de la cantidad de parametros que reciba, aunque también puede suceder el caso de que no se defina ninguno. Que no se defina un método constructor, no significa que no exista; todas las clases cuentan por defecto con un método constructor que, al llamarse, inicializará a los valores por defecto los campos de dicha clase.

Algunos lenguajes también tienen destructores, que se ejecutan cuando un objeto deja de utilizarse. Gracias a esto, se puede liberar la memoria asignada a ese objeto.

En lenguajes de programación más modernos ya casi no se utilizá los destructores, ya que utilizan herramientas y mecanismos automáticos para realizar estas funciones. En los lenguajes de programación que no existan automatismos para destruir objetos, serán los programadores los responsables que deben utilizar los destructores en los momentos adecuados.

3. Encapsulación y visibilidad

La encapsulación consiste en proteger los datos internos de un objeto y controlar cómo se accede a ellos. Para conseguirlo, los atributos suelen declararse como privados y se proporcionan métodos públicos para consultar o modificar sus valores, tal y como se ha visto en el punto 2.3.1. Métodos de acceso o propiedades

La visibilidad determina la manera en que los miembros de una clase son accedidos desde otros puntos del programa. Es importante no solo para crear relaciones de herencia entre distintas clases, sino también para implementar los algoritmos.

La visibilidad se puede aplicar a nivel de los atributos, métodos, clases y la forma de estructurar el programa, en paquetes y librerías.

Distinguimos entre los siguientes tipos de visibilidad:

  • Público (+): accesible desde cualquier parte del programa.
  • Privado (-): accesible solo desde la propia clase.
  • Protegido (#): accesible desde la propia clase y aquellas clases que hereden de esta.

Existen tipos de lenguaje de programación que cuentan con algún tipo de visibilidad adicional.

Ejemplo:

pseudocodigo.psc
Clase CuentaBancaria
privado saldo
método ingresar(cantidad)
si cantidad > 0 entonces
saldo ← saldo + cantidad;
fin si
fin método
método consultarSaldo()
devolver saldo;
fin método
Fin Clase

En este ejemplo, no se modifica el saldo directamente desde fuera. Se usa el método ingresar, que puede validar la operación antes de cambiar el estado del objeto.

7. Relaciones entre clases

En un programa orientado a objetos, suele haber más de una clase y estas no suelen estar aisladas, si no que en muchas casos colaboran entre sí mediante relaciones para compartir información.

Existen diferentes tipos de relaciones entre clases,

Asociación

La asociación indica que una clase conoce o utiliza objetos de otra clase.

Ejemplo: un Profesor imparte una Asignatura.

Agregación

La agregación representa una relación de tipo “tiene un”, pero las partes pueden existir de forma independiente.

Ejemplo: un Equipo tiene Jugadores, pero un jugador puede existir aunque el equipo desaparezca.

Composición

La composición es una relación más fuerte. Las partes dependen del objeto principal.

Ejemplo: un Pedido tiene LineasPedido; si se elimina el pedido, sus líneas dejan de tener sentido dentro del sistema.

8. Herencia

La herencia permite crear una clase nueva a partir de otra existente. La clase nueva hereda atributos y métodos de la clase base, y puede añadir o modificar comportamientos.

Ejemplo:

Clase Animal
método respirar()
Escribir "Respirando";
fin método
Fin Clase
Clase Perro hereda Animal
método ladrar()
Escribir "Guau";
fin método
Fin Clase

En este caso, Perro hereda el método respirar de Animal y añade su propio método ladrar.

La herencia debe usarse cuando exista una relación clara de tipo “es un”. Por ejemplo, un perro es un animal. No conviene usar herencia solo para reutilizar código si la relación conceptual no es correcta.

9. Polimorfismo

El polimorfismo permite que una misma operación se comporte de forma diferente según el objeto que la ejecute.

Ejemplo:

pseudocodigo.psc
Clase Animal
método hacerSonido()
Escribir "Sonido genérico";
fin método
Fin Clase
Clase Perro hereda Animal
método hacerSonido()
Escribir "Guau"
fin método
Fin Clase
Clase Gato hereda Animal
método hacerSonido()
mostrar "Miau"
fin método
Fin Clase

Si tenemos una lista de animales, podemos llamar a hacerSonido() sin preocuparnos de si el objeto concreto es un perro o un gato. Cada objeto responderá según su propia implementación.

10. Diagrama de clases

El diagrama de clases es un tipo de diagrama UML que representa la estructura de un sistema orientado a objetos. Muestra clases, atributos, métodos y relaciones entre clases.

Puedes ampliar la base de UML en el tutorial UML.

En un diagrama de clases, una clase suele representarse como un rectángulo dividido en tres partes:

+---------------------+
| Coche |
+---------------------+
| - marca |
| - modelo |
| - velocidad |
+---------------------+
| + acelerar() |
| + frenar() |
+---------------------+

Los signos de visibilidad ayudan a identificar cómo se puede acceder a cada miembro:

  • + público
  • - privado
  • # protegido

Los diagramas de clases son útiles antes de programar porque permiten revisar el diseño y detectar responsabilidades mal repartidas o relaciones confusas.

11. Ejercicios

Ejercicio 1: Identificar clases y objetos

Piensa en una aplicación para gestionar una biblioteca. Propón tres clases y dos objetos concretos para cada una.

Ejercicio 2: Diseñar una clase

Diseña una clase Producto indicando al menos cuatro atributos y tres métodos, uno de ellos debe ser un constructor. Genera 2 objetos nuevo y explica qué estado tendrían.

Ejercicio 3: Encapsulación

Explica por qué no sería buena idea modificar directamente el saldo de una cuenta bancaria desde cualquier parte del programa. Muestra un ejemplo de cómo se podría implementar un método para ingresar dinero de forma segura.

Ejercicio 4: Herencia

Diseña una clase base Vehiculo y dos clases derivadas. Indica qué atributos o métodos compartirían y cuáles serían específicos.