1. Code
  2. Coding Fundamentals

Let's Go: Programación orientada a objetos en Golang

Scroll to top

Spanish (Español) translation by Carlos (you can also view the original English article)

Go es una extraña mezcla de viejas y nuevas ideas. Tiene un enfoque muy innovador que no teme descartar las nociones establecidas de «cómo hacer las cosas». Mucha gente ni siquiera está segura de si Go es un lenguaje orientado a objetos. Déjame aclarar eso ahora mismo. ¡Sí, lo es!

En este tutorial aprenderás acerca de todas las particularidades del diseño orientado a objetos en Go. Cómo se expresan en Go los pilares de la programación orientada a objetos como la encapsulación, la herencia y el polimorfismo, y cómo se compara Go con otros lenguajes.

La filosofía del diseño de Go

Las raíces de Go están basadas en C y, más ampliamente, en la familia Algol. Ken Thompson medio en broma dijo que Rob Pike, Robert Granger y él mismo se reunieron y decidieron que odiaban C++. Sea una broma o no, Go es muy diferente de C++. Detalles sobre eso más adelante. Go se trata de la simplicidad máxima. Esto lo explica de manera detallada Rob Pike en Menos es exponencialmente más.

Go frente a otros lenguajes

Go no tiene clases, objetos, excepciones ni plantillas. Tiene recolección de basura y concurrencia integrada. La omisión más notable en lo que respecta a la orientación a objetos es que en Go no hay una jerarquía de tipos. Esto contrasta con la mayoría de los lenguajes orientados a objetos como C++, Java, C#, Scala, e incluso con los lenguajes dinámicos como Python y Ruby.

Características del lenguaje orientado a objetos de Go

Go no tiene clases, pero tiene tipos. En particular, tiene estructuras. Las estructuras son tipos definidos por el usuario. Los tipos de estructura (con métodos) sirven para fines similares a las clases en otros idiomas.

Estructuras (Structs)

Una estructura define el estado. Aquí hay una estructura Creature. Tiene un campo de nombre (Name) y una bandera booleana llamada Real, que nos dice si es una creature real o imaginaria. Las estructuras únicamente mantienen el estado y no el comportamiento.

1
type Creature struct {
2
3
  Name string
4
5
  Real bool
6
7
}
8

Métodos

Los métodos son funciones que operan en tipos particulares. Tienen una cláusula de recepción que ordena el tipo de operación. Aquí hay un método Dump() que opera en estructuras Creature e imprime su estado:

1
func (c Creature) Dump() {
2
  fmt.Printf("Name: '%s', Real: %t\n", c.Name, c.Real)
3
}

Esta es una sintaxis no usual, pero es muy explícita y clara (a diferencia del "this" implícito o del confuso "self" de Python).

Incrustación

Puedes incrustar tipos anónimos unos dentro de otros. Si incrustas una estructura sin nombre, luego la estructura incrustada proporciona su estado (y métodos) a la estructura incrustada directamente. Por ejemplo, FlyingCreature tiene una estructura Creature sin nombre incrustada, lo que significa que FlyingCreature es una Creature.

1
type FlyingCreature struct {
2
  Creature
3
  WingSpan int
4
}

Ahora, si tienes una instancia de una FlyingCreature, puedes acceder directamente a sus atributos Name y Real.

1
dragon := &FlyingCreature{
2
    Creature{"Dragon", false, },
3
    15,
4
}
5
6
fmt.Println(dragon.Name)
7
fmt.Println(dragon.Real)
8
fmt.Println(dragon.WingSpan)

Interfaces

Las interfaces son el sello distintivo del soporte orientado a objetos de Go. Las interfaces son tipos que declaran conjuntos de métodos. Del mismo modo que las interfaces en otros lenguajes, no tienen implementación.

Los objetos que implementan todos los métodos de interfaz implementan automáticamente la interfaz. No hay herencia o subclasificación o palabra clave "implements". En el siguiente fragmento de código, el tipo Foo implementa la interfaz Fooer (por convención, los nombres de la interfaz Go terminan con "er").

1
type Fooer interface {
2
  Foo1()
3
  Foo2()
4
  Foo3()
5
}
6
7
type Foo struct {
8
}
9
10
func (f Foo) Foo1() {
11
    fmt.Println("Foo1() here")
12
}
13
14
func (f Foo) Foo2() {
15
    fmt.Println("Foo2() here")
16
}
17
18
func (f Foo) Foo3() {
19
    fmt.Println("Foo3() here")
20
}

Diseño orientado a objetos: la forma de Go

Vamos a ver cómo se compara Go con los pilares de la programación orientada a objetos: encapsulación, herencia y polimorfismo. Esas son las características de los lenguajes de programación basados en clases, que son los lenguajes de programación orientados a objetos más populares

En lo fundamental, los objetos son construcciones de lenguaje que tienen un estado y un comportamiento que opera sobre el estado y lo expone selectivamente a otras partes del programa.

Encapsulado

Go encapsula las cosas a nivel de paquete. Los nombres que empiezan con una letra minúscula únicamente son visibles dentro de ese paquete. Puedes ocultar cualquier cosa en un paquete privado y solamente exponer tipos específicos, interfaces y funciones de fábrica.

Por ejemplo, para ocultar aquí el tipo Foo anterior y exponer únicamente la interfaz, puedes cambiarle el nombre a foo en minúsculas y proporcionar una función NewFoo() que devuelve la interfaz pública Fooer:

1
type foo struct {
2
}
3
4
func (f foo) Foo1() {
5
    fmt.Println("Foo1() here")
6
}
7
8
func (f foo) Foo2() {
9
    fmt.Println("Foo2() here")
10
}
11
12
func (f foo) Foo3() {
13
    fmt.Println("Foo3() here")
14
}
15
16
func NewFoo() Fooer {
17
    return &Foo{}
18
}

Entonces el código de otro paquete puede utilizar NewFoo() y obtener acceso a una interfaz Fooer implementada por el tipo de foo interno:

1
f := NewFoo()
2
3
f.Foo1()
4
5
f.Foo2()
6
7
f.Foo3()

Herencia

La herencia o subclases siempre fue un tema controversial. Hay muchos problemas con la herencia de la implementación (a diferencia de la herencia de la interfaz). La herencia múltiple, como se ha implementado por C++, Python y otros lenguajes, sufre el problema del diamante, pero incluso la herencia única no es tarea fácil con el problema frágil de la clase base.

Los lenguajes modernos y el pensamiento orientado a objetos ahora favorecen la composición sobre la herencia. Go se lo toma en serio y no tiene ningún tipo de jerarquía. Te permite compartir detalles de implementación a través de la composición. Pero Go, en un giro muy extraño (que posiblemente se originó por preocupaciones pragmáticas), permite la composición anónima mediante la incrustación.

En efecto, la composición mediante la incrustación de un tipo anónimo es equivalente a la herencia de la implementación. Una estructura incrustada es tan frágil como una clase base. También puedes incrustar una interfaz, lo que equivale a heredar de una interfaz en lenguajes como Java o C++. Incluso puede provocar un error de tiempo de ejecución que no se descubra en el momento de la compilación si el tipo de incrustación no implementa todos los métodos de interfaz.

Aquí SuperFoo incrusta la interfaz Fooer, pero no implementa sus métodos. El compilador de Go encantadamente te permitirá crear un nuevo SuperFoo y llamar a los métodos Fooer, pero obviamente fallará en tiempo de ejecución. Esto compila:

1
type SuperFooer struct {
2
  Fooer
3
}
4
5
func main() {
6
  s := SuperFooer{}
7
  s.Foo2()

Ejecutar este programa provoca pánico:

1
panic: runtime error: invalid memory address or nil pointer dereference
2
[signal 0xb code=0x1 addr=0x28 pc=0x2a78]
3
4
goroutine 1 [running]:
5
panic(0xde180, 0xc82000a0d0)
6
  /usr/local/Cellar/go/1.6/libexec/src/runtime/panic.go:464 +0x3e6
7
main.main()
8
  /Users/gigi/Documents/dev/go/src/github.com/oop_test/main.go:104 +0x48
9
exit status 2
10
11
Process finished with exit code 1

Polimorfismo

El polimorfismo es la esencia de la programación orientada a objetos: la capacidad de tratar objetos de diferentes tipos de forma uniforme siempre que se adhieran a la misma interfaz. Las interfaces de Go brindan esta capacidad de una manera muy directa e intuitiva.

Aquí tienes un ejemplo elaborado en el que se crean múltiples creatures (¡y una door!) que implementan la interfaz Dumper se crean y se almacenan en una fracción y luego se llama al método Dump() para cada una. También notarás diferentes estilos de instanciar los objetos.

1
package main
2
3
import "fmt"
4
5
type Creature struct {
6
  Name string
7
  Real bool
8
}
9
10
func Dump(c*Creature) {
11
  fmt.Printf("Name: '%s', Real: %t\n", c.Name, c.Real)
12
}
13
14
func (c Creature) Dump() {
15
  fmt.Printf("Name: '%s', Real: %t\n", c.Name, c.Real)
16
}
17
18
type FlyingCreature struct {
19
  Creature
20
  WingSpan int
21
}
22
23
func (fc FlyingCreature) Dump() {
24
  fmt.Printf("Name: '%s', Real: %t, WingSpan: %d\n",
25
    fc.Name,
26
    fc.Real,
27
    fc.WingSpan)
28
}
29
30
type Unicorn struct {
31
  Creature
32
}
33
34
type Dragon struct {
35
  FlyingCreature
36
}
37
38
type Pterodactyl struct {
39
  FlyingCreature
40
}
41
42
func NewPterodactyl(wingSpan int) *Pterodactyl {
43
  pet := &Pterodactyl{
44
    FlyingCreature{
45
      Creature{
46
        "Pterodactyl",
47
        true,
48
      },
49
      wingSpan,
50
    },
51
  }
52
  return pet
53
}
54
55
type Dumper interface {
56
  Dump()
57
}
58
59
type Door struct {
60
  Thickness int
61
  Color     string
62
}
63
64
func (d Door) Dump() {
65
  fmt.Printf("Door => Thickness: %d, Color: %s", d.Thickness, d.Color)
66
}
67
68
func main() {
69
  creature := &Creature{
70
    "some creature",
71
    false,
72
  }
73
74
  uni := Unicorn{
75
    Creature{
76
      "Unicorn",
77
      false,
78
    },
79
  }
80
81
  pet1 := &Pterodactyl{
82
    FlyingCreature{
83
      Creature{
84
        "Pterodactyl",
85
        true,
86
      },
87
      5,
88
    },
89
  }
90
91
  pet2 := NewPterodactyl(8)
92
93
  door := &Door{3, "red"}
94
95
  Dump(creature)
96
  creature.Dump()
97
  uni.Dump()
98
  pet1.Dump()
99
  pet2.Dump()
100
101
  creatures := []Creature{
102
    *creature,
103
    uni.Creature,
104
    pet1.Creature,
105
    pet2.Creature}
106
  fmt.Println("Dump() through Creature embedded type")
107
  for _, creature := range creatures {
108
    creature.Dump()
109
  }
110
111
  dumpers := []Dumper{creature, uni, pet1, pet2, door}
112
  fmt.Println("Dump() through Dumper interface")
113
  for _, dumper := range dumpers {
114
    dumper.Dump()
115
  }
116
}

Conclusión

Go es un verdadero lenguaje de programación orientado a objetos. Permite el modelado basado en objetos y promueve la mejor práctica para utilizar interfaces en vez de jerarquías de tipos concretos. Go tomó algunas decisiones sintácticas atípicas, pero en general, trabajar con tipos, métodos e interfaces se siente simple, ligero y natural.

La incrustación no es muy pura, pero aparentemente el pragmatismo estaba en juego, y la incrustación se proporcionó en lugar de únicamente la composición por nombre.