← CC2008

POO en otros lenguajes

Semestre 02, 2026

Java no inventó la POO

  • La Programación Orientada a Objetos es un paradigma, no un lenguaje.
  • Simula 67 introdujo las clases y los objetos casi treinta años antes que Java.
  • Smalltalk formalizó el envío de mensajes y C++ llevó la POO al mundo de los sistemas.
  • Ver cómo otros lenguajes resuelven los mismos problemas sirve para separar el concepto de la sintaxis.

Los pilares son portátiles

Abstracción: modelar lo esencial y esconder el resto.
Encapsulamiento: controlar quién toca los datos.
Herencia: reutilizar y especializar comportamiento.
Polimorfismo: un mismo contrato, muchas implementaciones.
Lo que cambia

Qué tan estricto es el compilador, cuánta ceremonia exige el lenguaje y qué mecanismo usa para el polimorfismo.

Mapa rápido

C#, el pariente cercano de Java, con azúcar sintáctica que elimina código repetitivo.
Python, dinámico, permisivo y con herencia múltiple real.
Rust, sin herencia, con traits y composición.
PHP, herencia simple con traits para reutilizar sin heredar.

C#

  • Creado por Microsoft en 2000 y diseñado por Anders Hejlsberg, el mismo autor de Turbo Pascal y Delphi.
  • Corre sobre la plataforma .NET y compila a un lenguaje intermedio, igual que Java al bytecode.
  • La sintaxis es tan parecida a Java que se lee sin traducción.
  • Las diferencias interesantes están en lo que C# agregó después.

Una clase en C#


public class Estudiante
{
    public string Nombre { get; set; }
    public int Carne { get; init; }

    public Estudiante(string nombre, int carne)
    {
        Nombre = nombre;
        Carne = carne;
    }

    public override string ToString() => $"{Carne} - {Nombre}";
}

Propiedades

En Java, exponer un atributo de forma controlada obliga a escribir un getter y un setter por cada campo.


// Java
private String nombre;
public String getNombre() { return nombre; }
public void setNombre(String n) { this.nombre = n; }

// C#
public string Nombre { get; set; }
Ventaja

La propiedad se usa como si fuera un atributo público, pero por debajo sigue siendo una llamada a método. Se puede agregar validación después sin romper a quien la usa.

virtual y override explícitos

En Java todo método de instancia se puede sobrescribir por defecto. En C# hay que pedirlo y aceptarlo.


public class Animal
{
    public virtual void HacerSonido() => Console.WriteLine("...");
}

public class Perro : Animal
{
    public override void HacerSonido() => Console.WriteLine("Guau");
}
Por qué importa

El diseño de la clase base queda documentado en el código. Un método sin virtual es un contrato cerrado y el compilador lo defiende.

Records

Para clases que solo transportan datos, C# genera el constructor, la comparación por valor y la representación en texto.


public record Punto(double X, double Y);

var p1 = new Punto(1, 2);
var p2 = new Punto(1, 2);

Console.WriteLine(p1 == p2);  // True, compara por valor
Console.WriteLine(p1);        // Punto { X = 1, Y = 2 }
Dato

Java tiene record desde la versión 16, así que esta idea ya viajó de regreso.

C# también tiene herencia simple

  • La documentación oficial es explícita en que una clase derivada puede tener una sola clase base directa.
  • Lo que sí ofrece es implementar varias interfaces, y desde C# 8, interfaces con implementación por defecto.

public interface IFigura
{
    double Area();
    void Mostrar() => Console.WriteLine($"Area: {Area()}");
}
Corrección

El lenguaje con herencia múltiple real de esta presentación es Python. C++ también la tiene, y de ahí viene el problema del diamante que Java decidió evitar.

Ventajas de C#

LINQ para consultar colecciones de forma declarativa.
async y await integrados en el lenguaje.
Structs para tipos por valor, sin pasar por el heap.
Sobrecarga de operadores.
Métodos de extensión para agregar comportamiento a clases ajenas.
Tipos anulables verificados por el compilador.

El problema del diamante

classDiagram
    direction TB
    class Dispositivo {
        +encender()
    }
    class Camara {
        +encender()
    }
    class Microfono {
        +encender()
    }
    Camara --|> Dispositivo
    Microfono --|> Dispositivo
    Celular --|> Camara
    Celular --|> Microfono
Definición

Una clase hereda de dos clases que a su vez heredan de una misma tercera, y las dos sobrescriben el mismo método. Celular no define encender, lo hereda, y Dispositivo le llega por dos caminos distintos. Esa forma de rombo es la que le da nombre al problema.

Lo que el lenguaje tiene que decidir

1Precedencia
  • Camara y Microfono definen encender. Al llamarlo sobre un Celular hay que elegir uno de los dos, y esa elección no está escrita en ninguna de las cuatro clases.
2Repetición
  • Los dos padres delegan en la clase de arriba. Si cada rama sube por su cuenta hasta la raíz, el encender de Dispositivo termina ejecutándose dos veces.
3Estado
  • Si Dispositivo lleva un contador o abre un recurso, esa segunda ejecución deja el objeto en un estado que nadie escribió y que nadie revisó.

Los lenguajes expuestos

C++ permite herencia múltiple de clases sin restricciones. Es el caso original.
Python permite herencia múltiple y fija el orden con una linearización.
Eiffel la llama herencia repetida y obliga a desambiguar con rename y select.
Perl permite herencia múltiple y desde la versión 5.10 ofrece C3.
Scala mezcla muchos traits y ordena el resultado con una linearización.
PHP mezcla muchos traits y obliga a elegir a mano cuando chocan.

Dos formas del mismo conflicto

En C++, Python, Eiffel y Perl el conflicto nace de un rombo, una misma raíz que llega por varios caminos.
En Scala y PHP no hay rombo, solo bloques planos que traen el mismo nombre, así que la resolución es más simple.

Seis respuestas distintas

Java lo prohíbe y permite una sola clase base con varias interfaces.
C# hace lo mismo y exige resolver a mano los conflictos entre interfaces con implementación por defecto.
Python lo permite y lo resuelve con el MRO.
Rust lo elimina de raíz al no tener herencia.
PHP lo prohíbe entre clases, y cuando aparece entre traits obliga a elegir con insteadof.
C++ lo permite y ofrece herencia virtual para desambiguar.

Python

  • Python es multiparadigma. Se puede escribir un script sin una sola clase o un sistema completo orientado a objetos.
  • La diferencia más visible frente a Java es la ausencia de tipos declarados y de modificadores de acceso.
  • No hay new, no hay public y no hay tipo de retorno.
  • El parámetro self es explícito y equivale al this de Java.

Una clase en Python


class Estudiante:
    def __init__(self, nombre, carne):
        self.nombre = nombre
        self.carne = carne

    def __str__(self):
        return f"{self.carne} - {self.nombre}"

e = Estudiante("Ana", 24001)
print(e)
Métodos especiales

Los nombres rodeados de guiones bajos conectan la clase con el lenguaje. Definir __str__ es el equivalente de sobrescribir toString en Java.

Encapsulamiento por convención

La documentación oficial lo dice sin rodeos, las variables de instancia privadas que no se puedan acceder desde fuera no existen en Python.


class Cuenta:
    def __init__(self):
        self.__saldo = 0

c = Cuenta()
print(c.__saldo)          # AttributeError
print(c._Cuenta__saldo)   # 0, sigue ahi
Name mangling

Un nombre con dos guiones bajos se renombra a _Clase__nombre. Está diseñado para evitar choques entre clase padre e hija, no para imponer seguridad.

Propiedades en Python

El decorador property cumple el mismo papel que las propiedades de C#.


class Circulo:
    def __init__(self, radio):
        self._radio = radio

    @property
    def area(self):
        return 3.14159 * self._radio ** 2

c = Circulo(2)
print(c.area)   # se lee como atributo, se ejecuta como metodo

Herencia múltiple real

Aquí está la diferencia grande con Java. Una clase de Python puede heredar de varias clases a la vez.


class Dispositivo:
    def encender(self):
        print("Encendiendo dispositivo")

class Camara(Dispositivo):
    def encender(self):
        print("Encendiendo camara")
        super().encender()

class Microfono(Dispositivo):
    def encender(self):
        print("Encendiendo microfono")
        super().encender()

class Celular(Camara, Microfono):
    pass

Celular().encender()

Duck typing

Python no exige que una clase declare que implementa una interfaz. Si el objeto tiene el método, sirve.


class Pato:
    def hablar(self):
        return "Cuac"

class Robot:
    def hablar(self):
        return "Beep"

for x in [Pato(), Robot()]:
    print(x.hablar())
El costo

Ni Pato ni Robot comparten una clase base. El polimorfismo funciona por la forma del objeto y no por su tipo declarado, así que el error aparece en tiempo de ejecución y no en compilación.

Ventajas de Python

Sintaxis mínima, mucho menos código para el mismo modelo.
Herencia múltiple con un orden de resolución bien definido.
Métodos especiales que integran las clases propias con el lenguaje.
Ecosistema enorme en ciencia de datos y automatización.
Ideal para prototipar un diseño antes de llevarlo a un lenguaje estricto.
Sin verificación estática, los errores de tipo se descubren corriendo el programa.

Rust

  • Lenguaje de sistemas enfocado en seguridad de memoria sin recolector de basura.
  • Tiene objetos, porque los structs y los enums guardan datos y los bloques impl les dan métodos.
  • Tiene encapsulamiento, porque todo es privado por defecto y se expone con pub.
  • No tiene herencia. El Rust Book dice que no hay forma de definir un struct que herede los campos y los métodos de otro.

Traits


trait RayIntersect {
    fn ray_intersect(&self, origen: &Vec3, direccion: &Vec3) -> bool;
}
El contrato

Un trait declara qué método debe tener un tipo y con qué firma, sin decir cómo lo resuelve. Es lo más parecido a una interfaz de Java.

Cada tipo trae su propia implementación


impl RayIntersect for Sphere {
    fn ray_intersect(&self, origen: &Vec3, direccion: &Vec3) -> bool {
        // la matemática propia de la esfera
    }
}

impl RayIntersect for Cube {
    fn ray_intersect(&self, origen: &Vec3, direccion: &Vec3) -> bool {
        // la matemática propia del cubo
    }
}
Un contrato, muchas versiones

Cada tipo resuelve el método a su manera dentro de su propio bloque impl. Sphere y Cube no heredan de nada ni comparten una clase base, responden al mismo mensaje solo porque los dos implementan el mismo trait.

Composición sobre herencia

Cuando una clase de Java heredaría, Rust incrusta el otro tipo como campo y delega.


struct Motor {
    caballos: u32,
}

struct Carro {
    motor: Motor,
    placa: String,
}

impl Carro {
    fn potencia(&self) -> u32 {
        self.motor.caballos
    }
}
Conexión

Es la relación tiene un que ya vieron en relaciones entre clases, elevada a única opción del lenguaje.

Ventajas de Rust

Sin recolector de basura y sin punteros nulos.
El compilador garantiza que no haya condiciones de carrera entre hilos.
Los traits se pueden implementar sobre tipos ajenos.
Enums con datos asociados que hacen innecesarias muchas jerarquías de clases.
Obliga a diseñar por composición, evitando jerarquías frágiles.
Curva de aprendizaje pronunciada por el modelo de propiedad de la memoria.

PHP

  • Rasmus Lerdorf lo creó en 1994 como un conjunto de scripts para su página personal.
  • El modelo de objetos que se usa hoy llegó con PHP 5 en 2004 y se reescribió por completo para PHP 7.
  • Tiene tipos declarados, visibilidad verificada por el motor, clases abstractas, interfaces, enums y traits.
  • La mayoría de los sitios web del mundo corre sobre PHP, casi siempre a través de frameworks orientados a objetos como Laravel o Symfony.
Nota de lectura

Todo el código de estas slides vive dentro de un archivo con extensión php, después de la etiqueta de apertura del lenguaje. Se omite aquí para dejar el modelo de objetos a la vista.

Una clase en PHP


class Estudiante
{
    public function __construct(
        public string $nombre,
        public readonly int $carne
    ) {}

    public function __toString(): string
    {
        return "{$this->carne} - {$this->nombre}";
    }
}

$e = new Estudiante("Ana", 24001);
echo $e;

Visibilidad verificada

Aquí está la diferencia grande con Python. El encapsulamiento de PHP no es una convención, lo hace cumplir el motor.


class Cuenta
{
    private float $saldo = 0;

    public function depositar(float $monto): void
    {
        $this->saldo += $monto;
    }
}

$c = new Cuenta();
$c->saldo = 1000;   // Error: Cannot access private property
Tres niveles

Las palabras public, protected y private significan exactamente lo mismo que en Java, y el error aparece en tiempo de ejecución porque PHP no tiene una fase de compilación separada.

Herencia simple y clases abstractas


abstract class Animal
{
    public function __construct(protected string $nombre) {}

    abstract public function hacerSonido(): string;

    public function presentarse(): string
    {
        return "{$this->nombre} dice {$this->hacerSonido()}";
    }
}

final class Perro extends Animal
{
    public function hacerSonido(): string { return "Guau"; }
}
Igual que Java

Una clase extiende a lo sumo una clase padre. Las palabras abstract y final tienen el mismo significado, y parent sustituye a super para llamar al método de la clase base.

Interfaces múltiples


interface Figura { public function area(): float; }

interface Dibujable { public function dibujar(): void; }

class Circulo implements Figura, Dibujable
{
    public function __construct(private float $radio) {}

    public function area(): float
    {
        return M_PI * $this->radio ** 2;
    }

    public function dibujar(): void
    {
        echo "Circulo de area {$this->area()}";
    }
}
Sin límite

Una clase implementa tantas interfaces como necesite, y una interfaz puede extender a varias interfaces a la vez. Es la misma salida que eligió Java para esquivar el problema del diamante.

Traits, reutilizar sin heredar

La documentación oficial define el trait como un mecanismo para reducir las limitaciones de la herencia simple, reutilizando conjuntos de métodos en clases independientes.


trait Registrable
{
    public function registrar(string $mensaje): void
    {
        echo date("H:i:s") . " " . $mensaje;
    }
}

trait Exportable
{
    public function aJson(): string
    {
        return json_encode(get_object_vars($this));
    }
}

class Pedido
{
    use Registrable, Exportable;
}

Un trait no es una clase padre

Qué hace

Inyecta sus métodos y atributos en la clase, como si estuvieran escritos ahí. Una clase puede usar todos los traits que quiera.

Qué no hace

No crea una relación es un. Pedido no es un Registrable y el trait no sirve como tipo, así que no reemplaza a las interfaces.

Patrón

Lo habitual es una interfaz para el contrato y un trait para el código.

Conflictos entre traits


trait Ave { public function moverse(): string { return "Vuela"; } }
trait Pez { public function moverse(): string { return "Nada"; } }

class Pinguino
{
    use Ave, Pez {
        Pez::moverse insteadof Ave;
        Ave::moverse as intentarVolar;
    }
}
Resolución manual

Cuando dos traits traen el mismo método, PHP obliga a elegir con insteadof y deja conservar el otro con as.

Precedencia

Sin conflicto el orden es la clase, luego los traits y al final el padre.

Enums con comportamiento


enum Estado: string
{
    case Pendiente = "pendiente";
    case Enviado   = "enviado";
    case Entregado = "entregado";

    public function etiqueta(): string
    {
        return match($this) {
            Estado::Pendiente => "En espera",
            Estado::Enviado   => "En camino",
            Estado::Entregado => "Recibido",
        };
    }
}

echo Estado::Enviado->etiqueta();
Desde PHP 8.1

Un enum es un tipo con métodos propios que incluso puede implementar interfaces. Reemplaza jerarquías de clases enteras que solo existían para representar un conjunto cerrado de valores.

Ventajas de PHP

Diseñado para la web. Cada petición arranca y termina limpia.
Traits para reutilizar comportamiento entre clases sin construir una jerarquía.
Métodos mágicos que integran las clases propias con el lenguaje.
Frameworks maduros como Laravel y Symfony, construidos por completo sobre POO.
Tipos declarados opcionales, con herramientas externas que los verifican antes de ejecutar.
Arrastra una biblioteca estándar con nombres y parámetros inconsistentes.

Comparación

ConceptoJavaC#PythonRustPHP
TipadoEstáticoEstáticoDinámicoEstáticoGradual
Herencia de clasesSimpleSimpleMúltipleNo existeSimple
InterfacesProtocolosTraits
Reuso horizontalInterfaces por defectoInterfaces por defectoHerencia múltipleTraitsTraits
SobrescrituraPor defectoExplícitaPor defectoNo aplicaPor defecto
PrivacidadCompiladorCompiladorConvenciónCompiladorMotor en ejecución
MemoriaRecolectorRecolectorRecolectorOwnershipRecolector
PolimorfismoInterfacesInterfacesDuck typingTraitsInterfaces

Qué llevarse

  • Los pilares de la POO son independientes del lenguaje. La sintaxis se olvida al cambiar de lenguaje, el diseño se transfiere.
  • La herencia múltiple no es un lujo que a Java le falte, es una decisión de diseño con un costo asociado.
  • La composición funciona en todos los lenguajes. Rust demuestra que se puede construir un sistema completo sin herencia.
  • Los traits de PHP y de Rust muestran que se puede compartir comportamiento entre clases sin construir una jerarquía.
  • Aprender un segundo lenguaje orientado a objetos es mucho más barato que aprender el primero.

Para pensar

Tomen el último proyecto de este curso y respondan estas preguntas.

1Interfaces
  • ¿Cuántas de sus interfaces desaparecerían si el lenguaje tuviera duck typing?
2Jerarquías
  • ¿Cuántas de sus jerarquías de herencia se podrían reescribir como composición?
3Seguridad
  • ¿Qué parte del código se volvería más segura con un compilador que verifique la propiedad de la memoria?