Translate

domingo, 30 de agosto de 2026

¿La herencia es realmente un pilar de la Orientación a Objetos?


En el post anterior cuestionábamos una idea bastante instalada: que un lenguaje necesita clases, herencia, encapsulamiento y polimorfismo para ser considerado orientado a objetos.


Si entendemos la orientación a objetos como una forma de modelar sistemas mediante objetos que encapsulan comportamiento y colaboran entre sí, aparece una pregunta inevitable: ¿La herencia es realmente necesaria?

La respuesta corta es: no.

Y dos lenguajes modernos lo muestran muy bien: Go y Rust.

Si tenemos:

Dog

Cat

Horse


podemos identificar características comunes y construir una abstracción:


            Animal

       /          \           \

     Dog      Cat    Horse

              

La herencia de clases es una de las formas tradicionales de representar esa relación:


Animal

  ↑

Dog


Pero la relación conceptual: “Dog puede comportarse como Animal”, no implica necesariamente que tengamos que utilizar herencia.

Podemos expresar la misma idea de otras maneras.

Y ahí aparecen los traits de Rust y las interfaces de Go.


Go no tiene clases ni herencia de clases.

Sin embargo, podemos definir una abstracción:


type Animal interface {

    Speak()

}


Y después diferentes tipos pueden satisfacer esa interfaz:


type Dog struct{}


func (Dog) Speak() {

    fmt.Println("Woof")

}


type Cat struct{}


func (Cat) Speak() {

    fmt.Println("Meow")

}


Podemos trabajar con la abstracción:


func makeSpeak(animal Animal) {

    animal.Speak()

}


Y utilizar:

makeSpeak(Dog{})

makeSpeak(Cat{})


No existe: Dog extends Animal


Pero sí existe algo conceptualmente importante:

Dog ───────► Animal

Cat ───────► Animal


Los tipos concretos satisfacen un contrato común.

Y esto permite polimorfismo.

La diferencia fundamental es que Go utiliza composición e interfaces, en lugar de una jerarquía de clases.


Rust sigue una idea similar.

Podemos definir:


trait Animal {

    fn speak(&self);

}


y después implementar ese comportamiento:


struct Dog;


impl Animal for Dog {

    fn speak(&self) {

        println!("Woof");

    }

}


Otro tipo puede hacer lo mismo:


struct Cat;


impl Animal for Cat {

    fn speak(&self) {

        println!("Meow");

    }

}


Podemos utilizar el trait como abstracción:


fn make_speak(animal: &dyn Animal) {

    animal.speak();

}


Y nuevamente tenemos polimorfismo:

        Animal

        /    \

      Dog    Cat


pero sin una jerarquía de herencia.

El mecanismo es diferente.

Entonces, ¿qué reemplaza a la herencia? En realidad, la pregunta está ligeramente mal formulada.

No necesitamos reemplazar la herencia.

Necesitamos resolver los problemas que tradicionalmente intentábamos resolver mediante herencia.


Por ejemplo:

Abstracción


Animal

 └── speak()


Go lo expresa mediante interfaces.

Rust lo expresa mediante traits.


Polimorfismo


Animal

  ↑

Dog

Cat


Ambos lenguajes permiten que diferentes tipos proporcionen el mismo comportamiento.


Reutilización

En lugar de crear jerarquías:


Animal

 ├── Dog

 │    └── BigDog

 │         └── SuperBigDog


podemos utilizar composición.


En Rust:

struct Engine;


struct Car {

    engine: Engine,

}


El Car no necesita ser un Engine. Simplemente tiene un Engine.

Esta diferencia parece pequeña, pero conceptualmente es enorme:

Herencia expresa “es un”. Composición expresa “tiene un”.


Y muchos diseños orientados a objetos pueden construirse perfectamente utilizando composición.

¿Entonces la herencia no sirve?

Por supuesto que sí.


La herencia puede ser muy útil cuando realmente existe una relación de especialización que queremos representar. El problema aparece cuando empezamos a utilizarla simplemente para:

  • reutilizar código;
  • compartir estado;
  • evitar duplicación;
  • construir jerarquías artificiales.


Por eso una buena regla de diseño suele ser: Prefer composition over inheritance.

No porque la herencia sea mala, sino porque reutilizar implementación y modelar una relación de subtipo son problemas diferentes.


Acá aparece una diferencia conceptual que muchas veces queda escondida.

Podemos tener:


Generalización

       │

       ▼

   Abstracción


y podemos representar esa generalización mediante: Herencia

pero también mediante:

  • Interfaces
  • Traits
  • Composición
  • Protocolos
  • Duck typing


Por lo tanto: La generalización es un concepto del modelo; la herencia es una de las herramientas que un lenguaje puede proporcionar para expresarla.

La abstracción no pertenece exclusivamente a las clases.


Entonces, ¿Go y Rust son orientados a objetos?

Acá la respuesta vuelve a depender de nuestra definición.


Si definimos OOP como: “programar utilizando clases y herencia”

entonces Go y Rust no son lenguajes orientados a objetos.


Pero esa definición es demasiado restrictiva.

Si consideramos fundamentales conceptos como:

  • encapsulamiento;
  • objetos con estado y comportamiento;
  • abstracción;
  • polimorfismo;
  • composición;
  • colaboración entre entidades;


entonces Go y Rust pueden expresar muchos diseños que reconocemos como orientados a objetos, aunque utilicen mecanismos diferentes.


Y esto nos lleva nuevamente a una conclusión importante: No deberíamos confundir orientación a objetos con el modelo de clases y herencia que popularizaron C++ y Java.


La herencia de clases es una implementación posible de determinadas ideas de OOP.

No necesariamente es la esencia de OOP.


¿Y Smalltalk?

Esto nos devuelve al origen.

En Smalltalk, la idea central de la orientación a objetos no era: clases + herencia + polimorfismo


sino algo mucho más cercano a: objetos + mensajes + comportamiento


Desde esa perspectiva, resulta menos sorprendente que lenguajes modernos puedan adoptar algunas ideas de OOP sin adoptar necesariamente las clases y la herencia tradicional.


Quizás entonces deberíamos cambiar la pregunta.

En lugar de: ¿Este lenguaje tiene herencia? preguntar: ¿Qué modelo utiliza el lenguaje para representar objetos, comportamiento, abstracción y colaboración?


Porque la ausencia de herencia no implica necesariamente la ausencia de orientación a objetos.

Y quizás esa sea la conclusión más importante:

La herencia es una herramienta para implementar determinadas relaciones entre objetos. La orientación a objetos es un modelo mucho más amplio que la herencia.


En definitiva, no necesitamos herencia para pensar en objetos.


Y tampoco necesitamos clases.

Pero entonces aparece una pregunta todavía más interesante: ¿Necesitamos siquiera clases para hacer Orientación a Objetos?


No hay comentarios.:

Publicar un comentario