Go
Interfaces
Buscar..
Observaciones
Las interfaces en Go son solo conjuntos de métodos fijos. Un tipo implementa implícitamente una interfaz si su conjunto de métodos es un superconjunto de la interfaz. No hay declaración de intenciones.
Interfaz simple
En Go, una interfaz es solo un conjunto de métodos. Usamos una interfaz para especificar un comportamiento de un objeto dado.
type Painter interface {
Paint()
}
El tipo de implementación no necesita declarar que está implementando la interfaz. Basta con definir métodos de la misma firma.
type Rembrandt struct{}
func (r Rembrandt) Paint() {
// use a lot of canvas here
}
Ahora podemos usar la estructura como interfaz.
var p Painter
p = Rembrandt{}
Una interfaz puede ser satisfecha (o implementada) por un número arbitrario de tipos. También un tipo puede implementar un número arbitrario de interfaces.
type Singer interface {
Sing()
}
type Writer interface {
Write()
}
type Human struct{}
func (h *Human) Sing() {
fmt.Println("singing")
}
func (h *Human) Write() {
fmt.Println("writing")
}
type OnlySinger struct{}
func (o *OnlySinger) Sing() {
fmt.Println("singing")
}
Aquí, The Human
struct satisface tanto la interfaz de Singer
como la de Writer
, pero la estructura OnlySinger
solo satisface la interfaz de Singer
.
Interfaz vacía
Hay un tipo de interfaz vacío, que no contiene métodos. Lo declaramos como interface{}
. Esto no contiene métodos por lo que cada type
satisface. Por lo tanto, la interfaz vacía puede contener cualquier valor de tipo.
var a interface{}
var i int = 5
s := "Hello world"
type StructType struct {
i, j int
k string
}
// all are valid statements
a = i
a = s
a = &StructType{1, 2, "hello"}
El caso de uso más común para las interfaces es asegurar que una variable admita uno o más comportamientos. Por el contrario, el caso de uso principal de la interfaz vacía es definir una variable que pueda contener cualquier valor, independientemente de su tipo concreto.
Para recuperar estos valores como sus tipos originales, solo tenemos que hacer
i = a.(int)
s = a.(string)
m := a.(*StructType)
o
i, ok := a.(int)
s, ok := a.(string)
m, ok := a.(*StructType)
ok
indica si la interface a
es convertible a un tipo dado. Si no es posible lanzar ok
, será false
.
Valores de interfaz
Si declara una variable de una interfaz, puede almacenar cualquier tipo de valor que implemente los métodos declarados por la interfaz.
Si declaramos h
de la interface Singer
, puede almacenar un valor de tipo Human
o OnlySinger.
Esto se debe a que todos ellos implementan los métodos especificados por la interfaz de Singer
.
var h Singer
h = &human{}
h.Sing()
Determinación del tipo subyacente desde la interfaz
A veces puede ser útil saber qué tipo subyacente se ha pasado. Esto se puede hacer con un interruptor de tipo. Esto supone que tenemos dos estructuras:
type Rembrandt struct{} func (r Rembrandt) Paint() {} type Picasso struct{} func (r Picasso) Paint() {}
Eso implementa la interfaz de Painter:
type Painter interface { Paint() }
Luego podemos usar este interruptor para determinar el tipo subyacente:
func WhichPainter(painter Painter) { switch painter.(type) { case Rembrandt: fmt.Println("The underlying type is Rembrandt") case Picasso: fmt.Println("The underlying type is Picasso") default: fmt.Println("Unknown type") } }
Verificación en tiempo de compilación si un tipo satisface una interfaz
Las interfaces e implementaciones (tipos que implementan una interfaz) están "desconectadas". Por lo tanto, es una pregunta legítima cómo verificar en tiempo de compilación si un tipo implementa una interfaz.
Una forma de pedirle al compilador que compruebe que el tipo T
implementa la interfaz I
es intentar una asignación utilizando el valor cero para T
o el puntero a T
, según corresponda. Y podemos elegir asignar al identificador en blanco para evitar la basura innecesaria:
type T struct{}
var _ I = T{} // Verify that T implements I.
var _ I = (*T)(nil) // Verify that *T implements I.
Si T
o *T
no implementa I
, será un error de tiempo de compilación.
Esta pregunta también aparece en las preguntas frecuentes oficiales: ¿Cómo puedo garantizar que mi tipo satisfaga una interfaz?
Tipo de interruptor
Los interruptores de tipo también se pueden usar para obtener una variable que coincida con el tipo de caso:
func convint(v interface{}) (int,error) {
switch u := v.(type) {
case int:
return u, nil
case float64:
return int(u), nil
case string:
return strconv(u)
default:
return 0, errors.New("Unsupported type")
}
}
Aserción de tipo
Puede acceder al tipo de datos real de la interfaz con Type Assertion.
interfaceVariable.(DataType)
Ejemplo de estructura MyType
que implementa la interfaz Subber
:
package main
import (
"fmt"
)
type Subber interface {
Sub(a, b int) int
}
type MyType struct {
Msg string
}
//Implement method Sub(a,b int) int
func (m *MyType) Sub(a, b int) int {
m.Msg = "SUB!!!"
return a - b;
}
func main() {
var interfaceVar Subber = &MyType{}
fmt.Println(interfaceVar.Sub(6,5))
fmt.Println(interfaceVar.(*MyType).Msg)
}
Sin .(*MyType)
no podríamos acceder a Msg
Field. Si probamos interfaceVar.Msg
mostrará un error de compilación:
interfaceVar.Msg undefined (type Subber has no field or method Msg)
Ir interfaces de un aspecto matemático
En matemáticas, especialmente la teoría de conjuntos , tenemos una colección de cosas que se denomina conjunto y nombramos esas cosas como elementos . Mostramos un conjunto con su nombre como A, B, C, ... o explícitamente poniendo su miembro en notación de refuerzo: {a, b, c, d, e}. Supongamos que tenemos un elemento arbitrario xy un conjunto Z, la pregunta clave es: "¿Cómo podemos entender que x es miembro de Z o no?". El matemático responde a esta pregunta con un concepto: Característica característica de un conjunto. La propiedad característica de un conjunto es una expresión que describe el conjunto completamente. Por ejemplo, tenemos un conjunto de números naturales que es {0, 1, 2, 3, 4, 5, ...}. Podemos describir este conjunto con esta expresión: {a n | a 0 = 0, a n = a n-1 +1}. En la última expresión a 0 = 0, a n = a n-1 +1 es la propiedad característica del conjunto de números naturales. Si tenemos esta expresión, podemos construir este conjunto completamente . Vamos a describir el conjunto de números pares de esta manera. Sabemos que este conjunto está formado por estos números: {0, 2, 4, 6, 8, 10, ...}. Con una mirada entendemos que todos estos números son también un número natural , en otras palabras, si agregamos algunas condiciones adicionales a la propiedad característica de los números naturales, podemos construir una nueva expresión que describa este conjunto . Entonces podemos describir con esta expresión: {n | n es un miembro de números naturales y el recordatorio de n en 2 es cero}. Ahora podemos crear un filtro que obtenga la propiedad característica de un conjunto y filtre algunos elementos deseados para devolver elementos de nuestro conjunto. Por ejemplo, si tenemos un filtro de números naturales, tanto los números naturales como los números pares pueden pasar este filtro, pero si tenemos un filtro de números pares, algunos elementos como 3 y 137871 no pueden pasar el filtro.
La definición de interfaz en Go es como definir la propiedad característica y el mecanismo de uso de interfaz como argumento de una función es como un filtro que detecta que el elemento es miembro de nuestro conjunto deseado o no. Permite describir este aspecto con código:
type Number interface {
IsNumber() bool // the implementation filter "meysam" from 3.14, 2 and 3
}
type NaturalNumber interface {
Number
IsNaturalNumber() bool // the implementation filter 3.14 from 2 and 3
}
type EvenNumber interface {
NaturalNumber
IsEvenNumber() bool // the implementation filter 3 from 2
}
La propiedad característica de Number
es todas las estructuras que tienen el método IsNumber
, para NaturalNumber
son todas las que tienen los métodos IsNumber
e IsNaturalNumber
y, finalmente, para EvenNumber
son todos los tipos que tienen los IsNumber
, IsNaturalNumber
e IsEvenNumber
. Gracias a esta interpretación de la interfaz, podemos entender fácilmente que, dado que la interface{}
no tiene ninguna propiedad característica, acepta todos los tipos (porque no tiene ningún filtro para distinguir los valores).