Semestre 02, 2026
Qué tan estricto es el compilador, cuánta ceremonia exige el lenguaje y qué mecanismo usa para el polimorfismo.
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}";
}
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; }
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.
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");
}
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.
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 }
Java tiene record desde la versión 16, así que esta idea ya viajó de regreso.
public interface IFigura
{
double Area();
void Mostrar() => Console.WriteLine($"Area: {Area()}");
}
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.
classDiagram
direction TB
class Dispositivo {
+encender()
}
class Camara {
+encender()
}
class Microfono {
+encender()
}
Camara --|> Dispositivo
Microfono --|> Dispositivo
Celular --|> Camara
Celular --|> Microfono
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.
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)
Los nombres rodeados de guiones bajos conectan la clase con el lenguaje. Definir __str__ es el equivalente de sobrescribir toString en Java.
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
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.
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
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()
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())
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.
trait RayIntersect {
fn ray_intersect(&self, origen: &Vec3, direccion: &Vec3) -> bool;
}
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.
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
}
}
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.
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
}
}
Es la relación tiene un que ya vieron en relaciones entre clases, elevada a única opción del lenguaje.
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.
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;
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
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.
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"; }
}
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.
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()}";
}
}
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.
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;
}
Inyecta sus métodos y atributos en la clase, como si estuvieran escritos ahí. Una clase puede usar todos los traits que quiera.
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.
Lo habitual es una interfaz para el contrato y un trait para el código.
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;
}
}
Cuando dos traits traen el mismo método, PHP obliga a elegir con insteadof y deja conservar el otro con as.
Sin conflicto el orden es la clase, luego los traits y al final el padre.
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();
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.
| Concepto | Java | C# | Python | Rust | PHP |
|---|---|---|---|---|---|
| Tipado | Estático | Estático | Dinámico | Estático | Gradual |
| Herencia de clases | Simple | Simple | Múltiple | No existe | Simple |
| Interfaces | Sí | Sí | Protocolos | Traits | Sí |
| Reuso horizontal | Interfaces por defecto | Interfaces por defecto | Herencia múltiple | Traits | Traits |
| Sobrescritura | Por defecto | Explícita | Por defecto | No aplica | Por defecto |
| Privacidad | Compilador | Compilador | Convención | Compilador | Motor en ejecución |
| Memoria | Recolector | Recolector | Recolector | Ownership | Recolector |
| Polimorfismo | Interfaces | Interfaces | Duck typing | Traits | Interfaces |
Tomen el último proyecto de este curso y respondan estas preguntas.