Adiós Clases, Hola Structs: Cómo Go Redefine la Programación Orientada a Objetos
De Clases a Structs: Cómo Go Reimagina la Programación Orientada a Objetos
Si vienes de otros lenguajes como Java, TypeScript, Kotlin... probablemente estés acostumbrado a la programación orientada a objetos (OOP) y al uso de clases. Aunque Go no es un lenguaje OOP tradicional, ofrece mecanismos poderosos para lograr muchos de los mismos objetivos de diseño. En este post, exploraremos cómo Go utiliza structs y composición para reimaginar los conceptos de OOP.
😲¿Por qué está esto en la sección de "Novedades"?
Aunque los conceptos que discutiremos no son nuevos en Go, siguen siendo "novedosos" para muchos desarrolladores que migran desde lenguajes OOP tradicionales. Entender estas diferencias es crucial para escribir Go idiomático y eficiente.
Un Ejemplo en TypeScript
Comencemos con una clase simple en TypeScript:
class Person { private name: string; private age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } public getName(): string { return this.name; } public getAge(): number { return this.age; } public sayHello(): void { console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`); } } // Uso const person = new Person("Alice", 30); person.sayHello();
Traduciendo a Go: Paso a Paso
Paso 1: Definir la Estructura
En Go, usamos struct
en lugar de class
:
type Person struct { name string age int }
Paso 2: Constructor
Go no tiene constructores en el sentido tradicional de OOP, pero podemos crear funciones que inicialicen y devuelvan nuevas instancias de nuestras estructuras. En Go, tenemos dos enfoques principales para esto: retornar un valor o retornar un puntero.
2.1 Constructor que retorna un valor
func NewPerson(name string, age int) Person { return Person{ name: name, age: age, } }
En este caso, NewPerson
retorna una copia de la estructura Person
. Esto es útil cuando:
- Quieres trabajar con una copia independiente de la estructura.
- La estructura es pequeña y la copia no impacta significativamente el rendimiento.
Uso:
person := NewPerson("Alice", 30)
2.2 Constructor que retorna un puntero
func NewPerson(name string, age int) *Person { return &Person{ name: name, age: age, } }
Este enfoque retorna un puntero a la estructura Person, nota los símbolos & y *
. Es preferible cuando:
- Quieres modificar la instancia original y que los cambios se reflejen en todos los lugares que la usan.
- La estructura es grande y quieres evitar copias innecesarias por razones de rendimiento.
- Necesitas representar la ausencia de un valor (un puntero puede ser
nil
).
Uso:
person := NewPerson("Alice", 30)
2.3 ¿Cuál elegir?
La elección entre valor y puntero depende de tu caso de uso específico:
- Valores:
- Pros: Semántica de copia, bueno para inmutabilidad.
- Contras: Puede ser ineficiente para estructuras grandes.
- Punteros:
- Pros: Eficiente para estructuras grandes, permite modificación in-situ.
- Contras: Introduce indirección, potencial para efectos secundarios no deseados.
Paso 3: Métodos
En Go, definimos métodos fuera de la estructura, y son llamados receivers:
func (p Person) GetName() string { return p.name } func (p Person) GetAge() int { return p.age } func (p Person) SayHello() { fmt.Printf("Hello, my name is %s and I'm %d years old.\n", p.name, p.age) }
Paso 4: Uso total
func main() { person := NewPerson("Alice", 30) person.SayHello() }
Ventajas del Enfoque de Go
- Simplicidad: Las estructuras en Go son simples y directas.
- Composición sobre Herencia: Go favorece la composición, lo que lleva a diseños más flexibles.
- Encapsulación a Nivel de Paquete: La visibilidad se controla a nivel de paquete, no de clase.
- Métodos en Cualquier Tipo: Puedes añadir métodos a cualquier tipo, no solo a estructuras.
- Interfaces Implícitas: Las interfaces en Go son implementadas implícitamente, lo que aumenta la flexibilidad.
Composición en Go
Go utiliza composición en lugar de herencia. Veamos un ejemplo:
type Address struct { Street string City string } type Employee struct { Person Address Role string } func NewEmployee(name string, age int, street, city, role string) Employee { return Employee{ Person: NewPerson(name, age), Address: Address{Street: street, City: city}, Role: role, } } func (e Employee) Introduce() { e.SayHello() fmt.Printf("I work as a %s and live at %s, %s.\n", e.Role, e.Street, e.City) }
En este ejemplo, Employee
está componiendo a Person
y Address
, obteniendo así sus campos y métodos.
Conclusión
Mientras que Go no sigue el paradigma OOP tradicional, ofrece mecanismos poderosos y flexibles para estructurar y organizar código. Al entender y adoptar estos conceptos, podrás escribir código Go más idiomático y eficiente.
Recuerda, la clave está en pensar en términos de comportamientos (interfaces) y composición, en lugar de jerarquías de clases y herencia.
¿Preguntas o comentarios? ¡Déjalos abajo! ¿En cuál equipo crees que estarás: En los que aman los structs, o en los que echarán de menos las clases (OOP)?
Comentarios