Seguro has escuchado del concepto Duck Typing. Es una característica de los lenguajes dinámicos, en que los tipos se determinan en tiempo de ejecución.
Considera este código en Python:
class Duck:
def swim(self):
print("Duck swimming")
def fly(self):
print("Duck flying")
class Whale:
def swim(self):
print("Whale swimming")
for animal in [Duck(), Whale()]:
animal.swim()
animal.fly()
El método swim,
al estar definido en ambas clases, es invocado por el intérprete en tiempo de ejecución y se ejecuta sin problemas. El método fly
solo está disponible en la clase Duck
, así que eso provocará una falla.
En los lenguajes de tipos estático, como Java, esto no pasa, porque los tipos se verifican en tiempo de compilación.
Go implementa algo distinto. La definición de qué es un pato se puede declarar a priori, mediante una interfaz, de este modo:
type Duck interface {
Fly()
Swim()
}
Nota que una interface se declara como un tipo. Entonces podemos usarla en una función del siguiente modo:
func LakeSimulation(ducks []Duck) {
for _, duck := range ducks {
duck.Fly()
duck.Swim()
}
}
LakeSimulation
es una función que nos permite implementar la simulación de un lago en que hay patos. Por ahora es bastante sencilla, simplemente invocamos los métodos Fly()
y Swim()
para cada elemento en el arreglo que definimos.
Ahora podemos crear una struct que implemente los métodos de la interfaz de este modo:
type BlackDuck struct {
name string
}
func (duck *BlackDuck) Fly() {
fmt.Printf("%s duck is flying\n", duck.name)
}
func (duck *BlackDuck) Swim() {
fmt.Printf("%s duck is swimming\n", duck.name)
}
func NewBlackDuck(name string) Duck {
return &BlackDuck{
name: name,
}
}
Y podemos probar esto del siguiente modo:
func main() {
ducks := []Duck{
ducks.NewBlackDuck("Daffy"),
ducks.NewBlackDuck("Donald"),
}
LakeSimulation(ducks)
}
Cisnes
Los cisnes se parecen a los patos, así que podemos aprovechar este hecho para agregar cisnes a nuestra simulación.
type Swan int
func (swan Swan) Fly() {
fmt.Println("Swan", swan, "is flying")
}
func (swan Swan) Swim() {
fmt.Println("Swan", swan, "is swimming")
}
Fíjate que el tipo Swan
es en el fondo un int
. Ojo, ya no es un tipo entero, no es un alias al tipo int
, es un nuevo tipo que hereda todo el comportamiento de un int
, pero que puede tener métodos, como Fly()
y Swan()
.
Nuestros cisnes son bastante anónimos, así que su identidad será el valor numérico que se le dé al declararlo.
De este modo podemos modificar nuestra simulación agregando un par de cisnes:
func main() {
ducks := []Duck{
NewBlackDuck("Daffy"),
NewBlackDuck("Donald"),
Swan(100),
Swan(42),
}
LakeSimulation(ducks)
}
Esto es muy interesante, en GO podemos asociar interfaces a cualquier tipo, basta con agregar los métodos que la componen en ese nuevo tipo. Es una forma de Duck Typing, en que definimos una “plantilla” (la interfaz) que nos indica qué vamos a entender como Pato, cualquier tipo que implemente esa plantilla entonces será un pato.
Esto es un segmento de la segunda parte de mi guía al lenguaje Go, que ha sido actualizada con este y otros conceptos.
Si te gustó te invito a compartir este artículo con amigos o colegas que les pueda interesar:
También puedes considerar suscribirte: