Saltar al contenido

¿Por qué se necesitan interfaces en Golang?

Presta atención porque en esta reseña hallarás el resultado que buscas.

Solución:

Las interfaces son un tema demasiado extenso para dar una respuesta completa aquí, pero algunas cosas para aclarar su uso.

Las interfaces son un herramienta. Si los usa o no, depende de usted, pero pueden hacer que el código sea más claro, más corto, más legible y pueden proporcionar una buena API entre paquetes o clientes (usuarios) y servidores (proveedores).

Sí, puedes crear el tuyo propio struct escriba, y puede “adjuntar” métodos, por ejemplo:

type Cat struct

func (c Cat) Say() string  return "meow" 

type Dog struct

func (d Dog) Say() string  return "woof" 

func main() 
    c := Cat
    fmt.Println("Cat says:", c.Say())
    d := Dog
    fmt.Println("Dog says:", d.Say())

Ya podemos ver alguna repetición en el código anterior: al hacer ambos Cat y Dog Di algo. ¿Podemos manejar ambos como el mismo tipo de entidad, como animal? Realmente no. Seguro que podemos manejar ambos como interface, pero si lo hacemos, no podemos llamar a su Say() método porque un valor de tipo interface no define ningún método.

Hay algunos semejanza en los dos tipos anteriores: ambos tienen un método Say() con la misma firma (parámetros y tipos de resultado). Podemos capturar esto con una interfaz:

type Sayer interface 
    Say() string

La interfaz contiene solo el firmas de los métodos, pero no sus implementación.

Tenga en cuenta que en Go a type implícitamente implementa una interfaz si su conjunto de métodos es un superconjunto de la interfaz. No hay declaración de intenciones. ¿Qué significa esto? Nuestro anterior Cat y Dog tipos ya implementan esto Sayer interfaz a pesar de que esta definición de interfaz ni siquiera existía cuando las escribimos anteriormente, y no las tocamos para marcarlas o algo así. Simplemente lo hacen.

Las interfaces especifican el comportamiento. Un tipo que implementa una interfaz significa que el tipo tiene todos los métodos que la interfaz “prescribe”.

Dado que ambos implementan Sayer, podemos manejar ambos como un valor de Sayer, tienen esto en común. Vea cómo podemos manejar ambos en unidad:

animals := []Sayerc, d
for _, a := range animals 
    fmt.Println(reflect.TypeOf(a).Name(), "says:", a.Say())

(Esa parte de reflejo es solo para obtener el nombre del tipo, no le dé mucha importancia a partir de ahora).

La parte importante es que podríamos manejar tanto Cat y Dog como del mismo tipo (un tipo de interfaz), y trabajar con ellos / usarlos. Si fueras rápido a crear tipos adicionales con un Say() método, podrían alinearse al lado Cat y Dog:

type Horse struct

func (h Horse) Say() string  return "neigh" 

animals = append(animals, Horse)
for _, a := range animals 
    fmt.Println(reflect.TypeOf(a).Name(), "says:", a.Say())

Digamos que desea escribir otro código que funcione con estos tipos. Una función de ayuda:

func MakeCatTalk(c Cat) 
    fmt.Println("Cat says:", c.Say())

Sí, la función anterior funciona con Cat y sin nada mas. Si desea algo similar, tendrá que escribirlo para cada tipo. No hace falta decir lo malo que es esto.

Sí, podrías escribirlo para tener un argumento de interface, y use la afirmación de tipo o los interruptores de tipo, lo que reduciría el número de funciones auxiliares, pero aún se ve realmente feo.

¿La solución? Sí, interfaces. Simplemente declare la función para tomar un valor de un tipo de interfaz que defina el comportamiento que desea hacer con ella, y eso es todo:

func MakeTalk(s Sayer) 
    fmt.Println(reflect.TypeOf(s).Name(), "says:", s.Say())

Puede llamar a esta función con un valor de Cat, Dog, Horse o cualquier otro tipo no conocido hasta ahora, que tenga un Say() método. Frio.

Pruebe estos ejemplos en Go Playground.

La interfaz proporciona algunos tipos de genéricos. Piense en escribir pato.

type Reader interface
     Read()


func callRead(r Reader)
      r.Read()


type A struct

func(_ A)Read()


type B struct

func(_ B)Read()

Está bien pasar la estructura A, y B para callRead, porque ambos implementan la interfaz Reader. Pero si no tiene interfaz, deberíamos escribir dos funciones para A y B.

func callRead(a A)
     a.Read()


func callRead2(b B)
     b.Read()

Como ya se ha dicho, las interfaces son una herramienta. No todos los paquetes se beneficiarán de ellos, pero para ciertas tareas de programación, las interfaces pueden ser extremadamente útiles para la abstracción y la creación de API de paquetes, y particularmente para código de biblioteca o código que puede implementarse en más de una forma.

Tomemos, por ejemplo, un paquete que se encarga de dibujar algunos gráficos primitivos en una pantalla. Podemos pensar en los requisitos esenciales básicos absolutos de una pantalla como poder dibujar un píxel, limpiar la pantalla, actualizar el contenido de la pantalla periódicamente, así como obtener información geométrica básica sobre la pantalla, como sus dimensiones actuales. Por lo tanto, una interfaz de “Pantalla” podría verse así;

type Screen interface 
    Dimensions() (w uint32, h uint32)
    Origin() (x uint32, y uint32)
    Clear()
    Refresh()
    Draw(color Color, point Point)

Ahora nuestro programa puede tener varios “controladores gráficos” diferentes que nuestro paquete de gráficos podría utilizar para cumplir con este requisito básico de una pantalla. Es posible que esté utilizando algún controlador de sistema operativo nativo, tal vez el paquete SDL2 y tal vez algo más. Y tal vez en su programa necesite admitir múltiples opciones para dibujar gráficos porque depende del entorno del sistema operativo, etc.

Entonces, puede definir tres estructuras, cada una de las cuales contiene los recursos necesarios para las rutinas de dibujo de pantalla subyacentes en el sistema operativo / bibliotecas, etc.

type SDLDriver struct 
    window *sdl.Window
    renderer *sdl.Renderer


type NativeDriver struct 
    someDataField *Whatever


type AnotherDriver struct 
    someDataField *Whatever

Y luego implementa en su código la interfaz de método para las tres estructuras de modo que cualquiera de estas tres estructuras pueda satisfacer los requisitos de la interfaz de pantalla

func (s SDLDriver) Dimensions() (w uint32, h uint32) 
    // implement Dimensions()


func (s SDLDriver) Origin() (x uint32, y uint32) 
    // implement Origin()


func (s SDLDriver) Clear() 
    // implement Clear()


func (s SDLDriver) Refresh() 
    // implement Refresh()


func (s SDLDriver) Draw(color Color, point Point) 
    // implement Draw()


...

func (s NativeDriver) Dimensions() (w uint32, h uint32) 
    // implement Dimensions()


func (s NativeDriver) Origin() (x uint32, y uint32) 
    // implement Origin()


func (s NativeDriver) Clear() 
    // implement Clear()


func (s NativeDriver) Refresh() 
    // implement Refresh()


func (s NativeDriver) Draw(color Color, point Point) 
    // implement Draw()


... and so on

Ahora, su programa externo realmente no debería cuidado CUÁL de estos controladores podría estar utilizando, siempre que pueda borrar, dibujar y actualizar la pantalla a través de una interfaz estándar. Esta es la abstracción. Proporciona a nivel de paquete el mínimo absoluto que se requiere para que el resto de su programa funcione. Sólo el código dentro de los gráficos necesita conocer todo el “meollo de la cuestión” de CÓMO funcionan las operaciones.

Por lo tanto, es posible que sepa qué controlador de pantalla necesita crear para el entorno dado, tal vez esto se decida al inicio de la ejecución en función de verificar lo que está disponible en el sistema de los usuarios. Decide que SDL2 es la mejor opción y crea una nueva instancia de SDLGraphics;

sdlGraphics, err := graphics.CreateSDLGraphics(0, 0, 800, 600)

Pero ahora puede crear una variable de tipo de pantalla a partir de esto;

var screen graphics.Screen = sdlGraphics

Y ahora tiene un tipo genérico de ‘Pantalla’ llamado ‘pantalla’ que implementa (suponiendo que los haya programado) los métodos Clear (), Draw (), Refresh (), Origin () y Dimensions (). A partir de este punto de su código, puede, con total confianza, emitir declaraciones como

screen.Clear()
screen.Refresh()

Y así sucesivamente … La belleza de esto es que tiene un tipo estándar llamado ‘Pantalla’ que el resto de su programa, que realmente no se preocupa por el funcionamiento interno de una biblioteca de gráficos, puede usar sin tener que pensar en eso. Puede pasar ‘Pantalla’ a cualquier función, etc.con la confianza de que funcionará.

Las interfaces son muy útiles y realmente te ayudan a pensar en la función de tu código en lugar de los datos en tus estructuras. ¡Y las interfaces pequeñas son mejores!

Por ejemplo, en lugar de tener un montón de operaciones de renderizado dentro de la interfaz de pantalla, quizás diseñe una segunda interfaz como esta;

type Renderer interface 
    Fill(rect Rect, color Color)
    DrawLine(x float64, y float64, color Color)
    ... and so on

Definitivamente lleva un tiempo acostumbrarse, dependiendo de su experiencia en programación y de los lenguajes que haya usado antes. Si ha sido un programador estricto de Python hasta ahora, encontrará Go bastante diferente, pero si ha estado usando Java / C ++, entonces descubrirá Go bastante rápido. Las interfaces le brindan una orientación a objetos sin la molestia que existe en otros lenguajes (por ejemplo, Java).

Calificaciones y reseñas

Si estás contento con lo expuesto, tienes la opción de dejar una crónica acerca de qué te ha parecido este enunciado.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *