Adiós Clases, Hola Structs: Cómo Go Redefine la Programación Orientada a Objetos

Golang no tiene clases

 

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) }
También aplica lo mismo con el tema de los punteros. Por ejemplo, estos receivers anteriores son con o sin punteros, ¿qué dices?

Exacto, son sin puntero, porque su misión es sólo retornar (Son getters)

Paso 4: Uso total

func main() { person := NewPerson("Alice", 30) person.SayHello() }

Ventajas del Enfoque de Go

  1. Simplicidad: Las estructuras en Go son simples y directas.
  2. Composición sobre Herencia: Go favorece la composición, lo que lleva a diseños más flexibles.
  3. Encapsulación a Nivel de Paquete: La visibilidad se controla a nivel de paquete, no de clase.
  4. Métodos en Cualquier Tipo: Puedes añadir métodos a cualquier tipo, no solo a estructuras.
  5. 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

Formulario de contacto

Enviar