6. Receptores de funciones. Interfaces¶
El concepto de interface es similar al de Java expresado de otro modo.
Supongamos un interfaz Forma** con dos implementaciones *Rectangulo y Circulo. El código en Java y Go es el siguiente:
// Definición de la interfaz
interface Forma {
double calcularArea(); // Método abstracto
}
// Implementación para la clase Rectangulo
class Rectangulo implements Forma {
private double ancho;
private double alto;
public Rectangulo(double ancho, double alto) {
this.ancho = ancho;
this.alto = alto;
}
@Override
public double calcularArea() {
return ancho * alto;
}
}
// Implementación para la clase Circulo
class Circulo implements Forma {
private double radio;
public Circulo(double radio) {
this.radio = radio;
}
@Override
public double calcularArea() {
return Math.PI * radio * radio;
}
}
// Clase principal para probar las implementaciones
public class Main {
public static void main(String[] args) {
// Crear una referencia de tipo Forma
Forma forma;
// Asignar un Rectangulo a la referencia
forma = new Rectangulo(10, 5);
System.out.println("Área del rectángulo: " + forma.calcularArea());
// Asignar un Circulo a la referencia
package main
import "fmt"
// Definición de la interfaz
type Forma interface {
Area() float64
}
// Estructura "Rectangulo"
type Rectangulo struct {
Ancho, Alto float64
}
// Implementación del método "Area" para Rectangulo
func (r Rectangulo) Area() float64 {
return r.Ancho * r.Alto
}
// Estructura "Circulo"
type Circulo struct {
Radio float64
}
// Implementación del método "Area" para Circulo
func (c Circulo) Area() float64 {
return 3.14 * c.Radio * c.Radio
}
func main() {
// Declaración de la interfaz Forma
var f Forma
// Asignar un Rectangulo a la interfaz
rect := Rectangulo{Ancho: 10, Alto: 5}
f = rect
fmt.Println("Área del rectángulo:", f.Area())
// Asignar un Circulo a la interfaz
circ := Circulo{Radio: 7}
f = circ
fmt.Println("Área del círculo:", f.Area())
}
En los dos casos se hace una asociación entre datos (clase en Java Struct en Go) y funciones (métodos en Java, funciones con receptor en Go)
La interfaz es una forma de definir un contrato que debe cumplir cualquier estructura que la implemente.
6.1 Receptores de funciones.¶
La sintaxis básica de un método receptor es la siguiente:
func (receptor Tipo) NombreDelMetodo(parametros) retorno {
// lógica del método
}
- receptor: Es una variable que representa la instancia del tipo sobre la cual se llama el método.
- Tipo: Es el tipo definido al que se asocia el método.
- NombreDelMetodo: Es el nombre del método.
- parametros: Argumentos opcionales que el método puede recibir.
- retorno: Tipo de valor que el método devuelve, si corresponde.
Se podría decir que en Java los métodos se declaran dentro del interfaz/clase y en Go se declaran fuera como cualquier función pero indicando el tipo de datos del receptor.
6.1.1 Tipos de receptores¶
6.1.1.1 Receptor por valor¶
Un método receptor por valor recibe una copia de la instancia del tipo, por lo que no puede modificar el valor original.
package main
import "fmt"
type Persona struct {
Nombre string
}
func (p Persona) Saludar() {
fmt.Println("Hola, soy", p.Nombre)
}
func main() {
p := Persona{Nombre: "Juan"}
p.Saludar() // Salida: Hola, soy Juan
}
6.1.1.2 Receptor por referencia¶
Un método receptor por puntero recibe la dirección de memoria de la instancia, permitiendo modificar el valor original.
package main
import "fmt"
type Persona struct {
Nombre string
}
func (p *Persona) CambiarNombre(nuevoNombre string) {
p.Nombre = nuevoNombre // Modifica el valor original
}
func main() {
p := Persona{Nombre: "Juan"}
p.CambiarNombre("Pedro")
fmt.Println("Nuevo nombre:", p.Nombre) // Salida: Nuevo nombre: Pedro
}
CambiarNombre recibe la dirección de memoria de la instancia Persona, al modificar la variable , también se modifica en el "main" para eso sirven los punteros.
6.1.2 Comparacón del receptor por valor y por puntero¶
Característica | Receptor por valor | Receptor por puntero |
---|---|---|
Copia | Trabaja con una copia | Trabaja con el valor original |
Modificaciones | No afectan el original | Pueden modificar el original |
Eficiencia | Menor eficiencia con tipos grandes | Más eficiente para estructuras grandes |
Uso de punteros | No requiere punteros | Requiere & al llamar |
6.2 Interface¶
El interface se define con la siguiente sintáxis:
type NombredelaInterfaz interface {
Metodo1() TipoDeRetorno
Metodo2(param Tipo) TipoDeRetorno
}
6.2.1 Implementación implicita de interface¶
En Go, la implementación de una interfaz es implícita. Esto significa que un tipo implementa una interfaz si define todos los métodos declarados en la interfaz. No es necesario declarar explícitamente que un tipo implementa una interfaz.
En el ejemplo anterior el interface "Forma" se implementa al declarar la función Area con receptor la estruct "Rectangulo"
// Definición de la interfaz
type Forma interface {
Area() float64
}
// Estructura "Rectangulo"
type Rectangulo struct {
Ancho, Alto float64
}
// Implementación del método "Area" para Rectangulo
func (r Rectangulo) Area() float64 {
return r.Ancho * r.Alto
}
6.2.2 Interfaz vacia¶
Una interfaz vacia puede contener un valor de cualquier tipo:
package main
import "fmt"
func imprimir(valor interface{}) {
fmt.Println("Valor:", valor)
}
func main() {
imprimir(42)
imprimir("Hola, Go!")
imprimir(3.14)
}
6.2.3 Composición de interfaces¶
Se pueden combinar interfaces para obtener una nueva
type Lector interface {
Leer() string
}
type Escritor interface {
Escribir(s string)
}
type LectorEscritor interface {
Lector
Escritor
}
6.2.4 Asercciones¶
En Go, una aserción de tipo permite acceder al valor subyacente que está almacenado en una variable de tipo interfaz. Es una operación similar a los casts de Java, pero con diferencias importantes en la forma en que se manejan los errores y los tipos.
La sintaxis básica:
t := i.(T)
- i: Es la variable de tipo interfaz.
- T: Es el tipo concreto que esperamos que contenga la variable i.
- t: Es la variable que recibe el valor subyacente.
-
Cómo funciona
-
La declaración verifica si el valor almacenado en i es del tipo T.
- Si la verificación es exitosa, el valor subyacente se asigna a la variable t.
- Si i no contiene un valor de tipo T, se produce un panic (error crítico que detiene la ejecución).
Ejemplo:
var i interface{} = "Hola, Go!"
t := i.(string) // Aserción exitosa
fmt.Println(t) // Salida: Hola, Go!
n := i.(int) // Error: provoca un panic porque i no contiene un int
6.2.5 Aserción de forma segura.¶
Para evitar un panic cuando no estamos seguros del tipo contenido en la interfaz, podemos usar una aserción segura, que devuelve dos valores:
t, ok := i.(T)
t: Es el valor subyacente de tipo T si la aserción tiene éxito. ok: Es un valor booleano que indica si la aserción fue exitosa.
La analogía en Java:
Object obj = "Hola, Java!";
if (obj instanceof String) {
String s = (String) obj; // Casting seguro
System.out.println("Éxito: " + s);
} else {
System.out.println("El objeto no es un String.");
}
6.3 Tipo switches¶
Un tipo switch es como una declaración switch regular, pero los casos en el tipo switch especifican tipos (no valores), y esos valores son comparados contra otros tipos de valores sostenidos por el valor de interfaz dado.
switch v := i.(type) {
case T:
// acá v es de tipo T
case S:
// acá v es de tipo S
default:
// sin coincidencia; acá v tiene el mismo tipo que i
}
-
La declaración en el tipo switch tiene la misma sintaxis que el tipo aserción i.(T), pero el tipo específico T es remplazado por la palabra clave type.
-
Esta declaración switch comprueba si el valor de la interfaz * contiene un valor del tipo T o S. En cada caso de T y S, la variable v será del tipo T o S respectivamente y mantienen el valor que tiene i. En el caso predeterminado (donde no hay ninguna coincidencia), la variable v es del mismo tipo de interfaz y valor que i.
6.3.1 Stringer¶
Uno de los interfaces más utilizados y definido en el paquete "fmt"
type Stringer interface {
String() string
}
Ejemplo:
package main
import "fmt"
type IPAddr [4]byte
// Agregar un método String() que devuelve la representación de la IP
func (ip IPAddr) String() string {
return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
}
func main() {
hosts := map[string]IPAddr{
"loopback": {127, 0, 0, 1},
"googleDNS": {8, 8, 8, 8},
}
for name, ip := range hosts {
fmt.Printf("%v: %v\n", name, ip)
}
}
6.4 Lectores¶
[todo]