Herencia: partir de otra clase#
La herencia permite crear una clase basada en otra, reutilizando su código y añadiendo o cambiando cosas. La clase base define lo común; las derivadas, lo específico.
// Clase base
public class Personaje
{
public int vida = 100;
public virtual void Atacar() // 'virtual' = las hijas pueden cambiarlo
{
Debug.Log("Ataque genérico");
}
}
// Mago HEREDA de Personaje (los dos puntos)
public class Mago : Personaje
{
public override void Atacar() // 'override' = redefine el comportamiento
{
Debug.Log("Lanza una bola de fuego");
}
}Mago ya tiene vida sin declararla: la hereda. Y personaliza Atacar.
virtual, override y base#
virtual: marca un método de la base como "sobreescribible".override: en la hija, redefine ese método.abstract: un método sin cuerpo que las hijas están obligadas a implementar (y la clase que lo tiene no se puede instanciar).base: llama a la versión de la clase padre.
public override void Atacar()
{
base.Atacar(); // ejecuta también el ataque genérico
Debug.Log("...y además, magia");
}Polimorfismo: tratar a todos por igual#
Como un Mago es un Personaje, puedes guardarlo en una variable del tipo base y llamar a Atacar(): se ejecutará la versión correcta automáticamente.
List<Personaje> equipo = new List<Personaje>();
equipo.Add(new Mago());
equipo.Add(new Guerrero());
foreach (Personaje p in equipo)
p.Atacar(); // cada uno ataca a su manera, sin preguntar qué esEsto es polimorfismo: el mismo código (p.Atacar()) produce comportamientos distintos según el objeto real.
Interfaces: un contrato#
Una interfaz define qué métodos debe tener una clase, sin decir cómo. Es un contrato: "si implementas IDañable, prometes tener un método RecibirDaño".
public interface IDañable
{
void RecibirDaño(int cantidad); // solo la firma, sin cuerpo
}
public class Enemigo : IDañable
{
public int vida = 100;
public void RecibirDaño(int cantidad) => vida -= cantidad;
}
public class Barril : IDañable
{
public void RecibirDaño(int cantidad) => Debug.Log("El barril se rompe");
}Tu espada no necesita saber si golpea a un enemigo, un barril o una puerta. Solo le pregunta: "¿eres IDañable?". Si lo eres, te daña.
if (hit.collider.TryGetComponent<IDañable>(out var objetivo))
objetivo.RecibirDaño(25);Añades mañana una clase Cristal : IDañable y la espada ya la daña sin tocar el código de la espada. Esto desacopla y hace el código extensible.
Herencia vs interfaz#
Herencia (: Clase) | Interfaz (: IContrato) | |
|---|---|---|
| Aporta | Código reutilizable + tipo | Solo un contrato (qué métodos) |
| Cuántas a la vez | Una sola clase base | Varias interfaces |
| Relación | "es un" (Mago es un Personaje) | "puede" (Barril puede ser dañado) |
| Cuándo | Compartir implementación común | Compartir capacidad sin imponer jerarquía |
La herencia profunda (clases que heredan de clases que heredan...) se vuelve rígida y frágil. La comunidad y el propio diseño de Unity favorecen la composición (juntar piezas/componentes) y las interfaces frente a árboles de herencia grandes. Úsala, pero no abuses: si tu jerarquía tiene 5 niveles, replantéalo.
Errores frecuentes#
- Olvidar
virtualen la base y no poder haceroverride. - Crear jerarquías de herencia demasiado profundas (frágiles).
- Usar herencia donde una interfaz encajaría mejor ("puede" vs "es un").
- Pensar que una clase puede heredar de varias clases (no; pero sí implementar varias interfaces).
Ponte a prueba#
Resumen#
La herencia crea clases a partir de otras reutilizando código (virtual/override/base), y el polimorfismo trata a las derivadas por su tipo base. Las interfaces definen contratos que desacoplan (una espada daña a todo lo IDañable). Herencia = "es un"; interfaz = "puede". Favorece la composición y las interfaces sobre las jerarquías profundas. Esto sostiene patrones como la máquina de estados.