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