Conceptos básicos de la Programación Orientada a Objetos (OOP o POO)
Conceptos básicos de la Programación Orientada a Objetos (OOP o POO)
OOP vs. Programación estructurada
- La programación orientada a objetos (POO) surge como una evolución de la programación estructurada.
- Busca modelar los problemas del mundo real utilizando objetos que combinan datos y comportamientos.
- En programación, el paradigma imperativo se basa en funciones y datos.
- El paradigma orientado a objetos se basa en objetos.
- Los objetos son el elemento básico y central de la programación orientada a objetos (OOP o POO).
( : Cada objeto en el programa representa algo del mundo real:
una persona, una cuenta bancaria, un pedido, una factura, etc.)
- Podemos hablar de un universo de discurso, entendido como el sistema que queremos automatizar mediante software.
- Un objeto es una entidad (concreta o abstracta) que desarrolla una actividad en un entorno determinado, dentro de un universo de discurso específico.
Definición
|
Objeto: cada elemento activo que identificamos dentro de un determinado universo de discurso.
- Serán nuestros componentes software para construir y ensamblar nuestros programas.
|
|
|
Ejemplo
|
|
|
- En un banco hay cuentas bancarias (objeto)
-
- Las cuentas bancarias se identifican con un número y un titular (nombre, apellidos y DNI) → atributos.
- Las cuentas pueden darse de alta o de baja, hacer extracciones, ingresos y transferencias → métodos.
|
|
|
Ejemplo
|
|
|
- En la gestión de una empresa, a nivel de información tenemos
-
- Empleados
- Nóminas
- Bases de datos
- Proveedores
- Facturas
- Pedidos
|
|
|
Comparativa con la aplicación del juego Mastermind
|
|
|
|
- Puede parecer una forma más compleja de programar, pero en realidad es una manera de dividir la naturaleza del problema en unidades independientes que pueden interactuar entre sí.
- Cada una de ellas tendrá una identidad propia, definida por los valores de sus atributos.
- Cada una tendrá también un comportamiento concreto, es decir, lo que sabe hacer y que el resto del programa o de los objetos podrá utilizar.
Elementos en la Programación Orientada a Objetos
- De lo dicho anteriormente deducimos que en la POO tenemos dos elementos fundamentales:
- Los atributos o características de la clase.
- Los métodos o comportamientos de la clase.
- Para crear objetos, previamente hay que definir su estructura.
- La definición de la estructura (atributos y métodos) de los componentes software se denomina clase.
Clase
- La descripción y especificación de los componentes software para su posterior uso en los programas.
- Una clase es la estructura o plantilla que define un tipo concreto de objetos.
- Los objetos son elementos concretos de mi sistema: instancias de esa clase, cargadas en memoria para ser utilizadas por un programa.
|
- Elementos de la POO
Atributo
- Son las características o datos de un objeto.
- Sus valores determinan el estado del objeto en un momento dado.
- Normalmente, al instanciar un objeto en memoria, lo primero que hacemos es asignar valores a sus atributos.
- Es recomendable que los atributos estén encapsulados, es decir, que sólo sean accesibles dentro del propio objeto (privados).
|
Método
- Especifican el comportamiento de los objetos.
- Permiten modificar o conocer el estado de un objeto mediante métodos getter y setter.
- Permiten que un objeto realice acciones dentro del sistema y se comunique con otros objetos.
- Los métodos representan las acciones que el objeto sabe hacer, los servicios que ofrece al sistema.
- También pueden incluir acciones internas que facilitan el funcionamiento del propio objeto.
- En PHP, los métodos se definen dentro de la clase y se invocan mediante el operador ->.
|
Métodos static vs no static
- En PHP, la idea de static es igual que en cualquier lenguaje de programación orientado a objetos.
- Cuando un elemento (atributo o método) es estático, ese elemento es compartido por todos los objetos de la clase y persiste en memoria con su contenido mientras exista al menos un objeto de esa clase.
- Como no pertenece a cada objeto individual, sino a la clase en general, también se les llama atributos o métodos de clase.
- Para acceder a un elemento estático, necesitamos nombrar la clase (no el objeto). En PHP podemos hacerlo usando el operador self o el propio nombre de la clase junto con el operador de resolución de ámbito ::.
Usar constantes y elementos estáticos
- Como ejemplo, vamos a implementar una clase Factura con los siguientes requisitos
-
- La factura tendrá una constante llamada IVA.
- Tendremos un atributo estático que especificará el número de facturas creadas.
- Los atributos de cada factura serán importe_bruto y fecha.
- Tendrá un método generarFactura() que mostrará:
- Factura de XXXX (el nombre se recibirá como argumento).
- Fecha (atributo).
- Importe base (atributo).
- IVA aplicado (constante de la clase).
- Total bruto (importe base más el IVA).
- En el archivo index.php crearemos 5 facturas, visualizaremos el número de facturas, eliminaremos dos de ellas y volveremos a visualizar el número de facturas. Finalmente imprimiremos las dos facturas restantes.
| Posible solución |
|---|
<?php
// Factura.php
class Factura
{
// Constante IVA
const IVA = 0.21;
// Atributo estático para contar el número de facturas
private static $numeroFacturas = 0;
// Atributos de instancia
private $importe_bruto;
private $fecha;
// Constructor
public function __construct($importe_bruto, $fecha)
{
$this->importe_bruto = $importe_bruto;
$this->fecha = $fecha;
self::$numeroFacturas++; // Incrementa el contador al crear una nueva factura
}
// Destructor
public function __destruct()
{
self::$numeroFacturas--; // Decrementa el contador al eliminar una factura
}
// Método para generar una factura
public function generarFactura($nombreCliente)
{
$ivaAplicado = $this->importe_bruto * self::IVA;
$totalBruto = $this->importe_bruto + $ivaAplicado;
echo "Factura de $nombreCliente<br/>";
echo "Fecha: {$this->fecha}<br/>";
echo "Importe base: {$this->importe_bruto} €<br/>";
echo "IVA aplicado (" . (self::IVA * 100) . "%): {$ivaAplicado} €<br/>";
echo "Total bruto: {$totalBruto} €<br/>";
echo "-----------------------------<br/>";
}
// Método estático para obtener el número de facturas actuales
public static function obtenerNumeroFacturas()
{
return self::$numeroFacturas;
}
}
// index.php
// Crear 5 facturas
$factura1 = new Factura(100, '2024-11-18');
$factura2 = new Factura(200, '2024-11-18');
$factura3 = new Factura(150, '2024-11-18');
$factura4 = new Factura(300, '2024-11-18');
$factura5 = new Factura(250, '2024-11-18');
// Visualizar el número de facturas
echo "Número de facturas actuales: " . Factura::obtenerNumeroFacturas() . "<br/><br/>";
// Eliminar dos facturas
unset($factura4);
unset($factura5);
// Visualizar el número de facturas tras eliminar dos
echo "Número de facturas tras eliminar: " . Factura::obtenerNumeroFacturas() . "<br/><br/>";
// Imprimir las facturas restantes
$factura1->generarFactura("Cliente 1");
$factura2->generarFactura("Cliente 2");
?>
|
|
- Es muy común tener un atributo static que cuente cuántos objetos existen de una clase.
- En la siguiente imagen se ilustra cómo al crear varios objetos, cada uno se instancia por separado en memoria:
- Cuando añadimos un elemento estático, solo habrá una copia en memoria, compartida por todos los objetos de la clase:
- Vemos el código y cómo se accede al elemento estático:
<?php
class Racional {
static public $cuenta_racionales = 0;
private $num; // Numerador
private $den; // Denominador
public function __construct($num, $den) {
self::$cuenta_racionales++;
$this->num = $num;
$this->den = $den;
}
}
?>
<?php
require_once "Racional.php";
$r1 = new Racional(5,4);
$r2 = new Racional(5,4);
$r3 = new Racional(5,4);
echo "<h1>Ahora tenemos " . Racional::$cuenta_racionales . " objetos Racional</h1>";
$r4 = new Racional(5,4);
$r5 = new Racional(5,4);
echo "<h1>Ahora tenemos " . Racional::$cuenta_racionales . " objetos Racional</h1>";
$r6 = new Racional(5,4);
$r7 = new Racional(5,4);
// Observa (y esto es propio de PHP) que puedo acceder a un elemento estático
// tanto a través del nombre de la clase como desde un objeto
echo "<h2>Podemos acceder con los objetos:</h2>";
echo "<h3>Según r1: " . $r1::$cuenta_racionales . "</h3>";
echo "<h3>Según r2: " . $r2::$cuenta_racionales . "</h3>";
echo "<h3>Según r3: " . $r3::$cuenta_racionales . "</h3>";
echo "<h3>Según la clase: " . Racional::$cuenta_racionales . "</h3>";
?>
---
- Vamos a implementar las operaciones con números racionales.
Antes, recordemos las operaciones básicas:
- Sumar

- Restar

- Multiplicar

- Dividir

- Si la operación la implementamos como un método no estático, lo que estaremos haciendo es sumar al objeto actual otro objeto Racional que pasamos como argumento.
Podemos modificar el objeto actual o devolver un nuevo objeto (lo más correcto).
/**
* Suma al racional actual el racional recibido como parámetro.
* @param Racional $n1
* @return Racional
*/
public function sumar(Racional $n1) {
$den = $this->den * $n1->getDen();
$num = $this->num * $n1->getDen() + $this->den * $n1->getNum();
return new Racional($num, $den);
}
$r1 = new Racional(7,6);
$r2 = new Racional(9,4);
$r3 = $r1->sumar($r2);
echo "$r1 + $r2 = $r3";
---
- Si la operación la realizamos como un método estático, estaremos sumando dos objetos Racional y devolviendo un nuevo objeto como resultado:
static public function sum_static(Racional $r1, Racional $r2){
$n = $r1->getNum() * $r2->getDen() + $r1->getDen() * $r2->getNum();
$d = $r1->getDen() * $r2->getDen();
return new Racional($n, $d);
}
$r1 = new Racional(7,6);
$r2 = new Racional(9,4);
$r3 = Racional::sum_static($r1, $r2);
echo "$r1 + $r2 = $r3";
Herencia
- La herencia es un mecanismo de programación que permite crear una jerarquía en los componentes software, que se van especializando.
- Es un principio de abstracción mediante el cual podemos crear una jerarquía de clases, con una raíz que contiene los elementos comunes y nodos que representan clases especializadas.
- La idea es definir una clase con ciertas características comunes (atributos, métodos). Posteriormente, crearemos otras clases a partir de la ya existente, heredando implícitamente los atributos y métodos como parte de su estructura o composición.
- Es una característica muy natural. Por ejemplo:
- Personas → (Médicos, Bailarines)
- Vehículos → (Terrestres → Coche, Moto) / (Acuáticos → Barco, Lancha)
- La herencia es una forma de obtener características comunes por separado y luego especializarlas, evitando redundancias.
- Facilita la reutilización y la adaptación del código.
- La herencia implica declarar jerarquías de clases
- En la raíz de la jerarquía establecemos la parte o estructura común a todas las clases, y posteriormente vamos especializando las diferencias de cada una.
- Todos los atributos y métodos de una clase superior (supertipo) que sean públicos o protegidos son heredados por todas las clases derivadas (subtipos).
|
- Para establecer una jerarquía, usamos la palabra reservada extends en las clases que heredan.
- Vamos a verlo con un ejemplo:
- Primero establecemos la clase Persona:
<?php
class Persona {
protected $nombre;
protected $direccion;
protected $edad;
protected $frase;
public function __construct(string $n, string $d, int $e){
$this->nombre = $n;
$this->direccion = $d;
$this->edad = $e;
}
public function establecer_frase(string $frase){
$this->frase = $frase;
}
public function hablar(){
echo $this->frase;
}
}
?>
- Ahora establecemos la clase que hereda de Persona:
Principios del uso de la herencia
- Todos los atributos y métodos públicos y protegidos del supertipo Persona son también de Sanitario.
- En un momento dado, podemos invocar métodos del supertipo usando el operador parent junto con el operador de resolución de ámbito ::.
|
<?php
class Sanitario extends Persona {
protected $centroSalud;
protected $yearTitulacion;
public function __construct(string $n, string $d, int $e, string $centro, int $year) {
parent::__construct($n, $d, $e);
$this->centroSalud = $centro;
$this->yearTitulacion = $year;
}
public function mostrarInfo(){
echo "$this->nombre trabaja en $this->centroSalud y obtuvo su título en $this->yearTitulacion.";
}
}
?>
---
- Vemos dos ejemplos para explicar de forma práctica este concepto.
|
|
Herencia: gestión de personal en un ambulatorio
|
|
|
- Se pide gestionar un ambulatorio.
- Para ello haremos solo el diagrama de clases y su implementación a nivel básico (sin entrar en detalles).
- Tras realizar el análisis, se determina que se pretende gestionar los datos de los empleados y anotar las acciones básicas que realizan.
- Encontramos los siguientes elementos, que especificamos como clases
-
- Conserjes
- Enfermeras
- Médicas
Las propiedades (atributos) y métodos de cada clase se representan en los siguientes diagramas:
- Claramente vemos que todos ellos comparten varios elementos comunes.
- Esto nos permite crear una clase genérica, por ejemplo personalAmbulatorio.
- Posteriormente crearemos especializaciones de esta clase con los elementos particulares.
El diagrama podría quedar así:
- Ver la aplicación ejecutándose:
http://manuel.infenlaces.com/dwes/ejercicios/T6_Ambulatorio/
| Posible index de uso |
|---|
<?php
spl_autoload_register(function ($nombre_clase) {
include $nombre_clase . '.php';
});
$medico1 = new Medica("María", "Martínez", "Casa de María", 29, "Cardiología");
$medico2 = new Medica("Luis", "Pérez", "Casa de Luis", 38, "Pediatría");
$medico3 = new Medica("Nieves", "Ruiz", "Casa de Nieves", 44, "Dermatología");
$conserje = new Conserje("Soledad", "Viruela", "Casa de Soledad", 58, "Mostrador Entrada");
$enfermera1 = new Enfermera("Javier", "Moreno", "Casa de Javier", "General", 1990);
$enfermera2 = new Enfermera("Luis", "Pérez", "Casa de Javier", "General", 1990);
$conserje->avisoEnfermera($enfermera1, "Realizar cura en brazo del Sr. Martínez NSS 50/2155441/35");
$enfermera1->avisoMedico($medico3, "Paciente con tos y fiebre");
$enfermera2->avisoMedico($medico2, "Paciente con fiebre y vómitos");
$enfermera2->avisoMedico($medico3, "Paciente con pie torcido; posible fractura");
$conserje->avisoMedico($medico1, "Visitar en casa por fiebre alta", "Visita");
$conserje->avisoMedico($medico2, "Persona mayor con poca movilidad", "Visita");
$conserje->avisoMedico($medico3, "Niño pequeño con fiebre", "Consulta");
?>
<html>
<head>
<meta charset="UTF-8">
<title>Gestión Ambulatorio</title>
<link rel="stylesheet" href="./stilo.css" type="text/css"/>
</head>
<body>
<?php
echo $medico3;
echo $medico1;
echo $medico2;
?>
</body>
</html>
|
|
Clases Abstractas
- Cuando realizamos jerarquías, muchas veces encontramos métodos comunes a varias clases.
Esto implicaría que ese método debería pertenecer a una superclase (o clase padre), de la que luego se heredará.
- Pero puede ocurrir que, aunque el concepto del método sea común a todas las clases, la forma de implementarlo sea diferente en cada una.
- En este caso, la forma correcta de proceder es especificar el método en la clase superior y dejar su implementación a las clases derivadas.
- El método especificado en la clase superior será un método sin código, conocido como método abstracto, y la clase donde se define pasa a ser una clase abstracta.
Clase abstracta
- Es aquella clase que tiene uno o más métodos abstractos.
|
Método abstracto
- Es un método que no tiene código asociado.
- Su implementación se realizará en las clases derivadas.
|
- Nunca podremos instanciar un objeto de una clase abstracta.
- Esto es lógico, ya que ese objeto no tendría instrucciones para ejecutar sus métodos abstractos.
|
- Vamos a plantear un ejemplo práctico:
|