Cómo definir e implementar una interfaz de Go

El modelo orientado a objetos de Go gira en torno a las interfaces. Personalmente creo que las interfaces son la construcción de lenguaje más importante y todas las decisiones de diseño deben centrarse primero en las interfaces. 

En este tutorial, aprenderá qué es una interfaz, la perspectiva de Go sobre las interfaces, cómo implementar una interfaz en Go y, finalmente, las limitaciones de las interfaces frente a los contratos..

¿Qué es una interfaz Go??

Una interfaz Go es un tipo que consta de una colección de firmas de métodos. Aquí hay un ejemplo de una interfaz Go:

tipo interfaz Serializable Serialize () (string, error) Deserialize (s string) error

los Serializable La interfaz tiene dos métodos. los Publicar por fascículos() método no toma argumentos y devuelve una cadena y un error, y el Deserializar () método toma una cadena y devuelve un error. Si has estado alrededor de la cuadra, el Serializable Es probable que la interfaz le resulte familiar desde otros idiomas, y puede adivinar que el Publicar por fascículos() método devuelve una versión serializada del objeto de destino que puede reconstruirse llamando Deserializar () y pasando el resultado de la llamada original a Publicar por fascículos().

Tenga en cuenta que no es necesario proporcionar la palabra clave "func" al principio de cada declaración de método. Go ya sabe que una interfaz solo puede contener métodos y no necesita ninguna ayuda de su parte diciéndole que es una "función".

Ir Interfaces Mejores Prácticas

Las interfaces Go son la mejor manera de construir la columna vertebral de su programa. Los objetos deben interactuar entre sí a través de interfaces y no a través de objetos concretos. Esto significa que debe construir un modelo de objeto para su programa que consiste solo en interfaces y tipos básicos u objetos de datos (estructuras cuyos miembros son tipos básicos u otros objetos de datos). Estas son algunas de las mejores prácticas que debe seguir con las interfaces..

Claras intenciones

Es importante que la intención detrás de cada método y la secuencia de llamadas sea clara y bien definida tanto para los que llaman como para los implementadores. No hay soporte a nivel de idioma en Go para eso. Lo discutiré más en la sección "Interfaz vs. Contrato" más adelante..

Inyección de dependencia

La inyección de dependencia significa que un objeto que interactúa con otro objeto a través de una interfaz obtendrá la interfaz desde el exterior como una función o un argumento de método y no creará el objeto (o llamará a una función que devuelve el objeto concreto). Tenga en cuenta que este principio se aplica también a las funciones independientes y no solo a los objetos. Una función debe recibir todas sus dependencias como interfaces. Por ejemplo:

escriba SomeInterface DoSomethingAesome () func foo (s SomeInterface) s.DoSomethingAwesome () 

Ahora, llamas función foo () con diferentes implementaciones de Un poco de interfaz, y funcionará con todos ellos..

Suerte

Obviamente, alguien tiene que crear los objetos concretos. Este es el trabajo de los objetos dedicados de fábrica. Las fábricas se utilizan en dos situaciones:

  1. Al comienzo del programa, las fábricas se utilizan para crear todos los objetos de larga duración cuya vida útil normalmente coincide con la vida útil del programa..
  2. Durante el tiempo de ejecución del programa, varios objetos a menudo necesitan instanciar objetos dinámicamente. Las fábricas deben ser utilizadas para este propósito también.

A menudo es útil proporcionar interfaces de fábrica dinámicas a objetos para mantener el patrón de interacción de solo interfaz. En el siguiente ejemplo, defino un Widget interfaz y un WidgetFactory interfaz que devuelve un Widget interfaz desde su CreateWidget () método. 

los PerformMainLogic () la función recibe un WidgetFactory interfaz de su interlocutor. Ahora es capaz de crear dinámicamente un nuevo widget basado en su especificación de widget e invoca su Widgetize () Método sin saber nada sobre su tipo concreto (qué estructura implementa la interfaz).

tipo interfaz de widget Widgetize () tipo interfaz de WidgetFactory CreateWidget (widgetSpec string) (Widget, error) func PerformMainLogic (factory WidgetFactory) … widgetSpec: = GetWidgetSpec () widget: = factroy.CreateWidget (widgetSpec) widget.Widgetize ( ) 

Probabilidad

La capacidad de prueba es una de las prácticas más importantes para el desarrollo de software adecuado. Las interfaces Go son el mejor mecanismo para soportar la capacidad de prueba en los programas Go. Para probar a fondo una función o un método, debe controlar y / o medir todas las entradas, salidas y efectos secundarios de la función bajo prueba.. 

Para el código no trivial que se comunica directamente con el sistema de archivos, el reloj del sistema, las bases de datos, los servicios remotos y la interfaz de usuario, es muy difícil de lograr. Pero, si toda la interacción pasa a través de interfaces, es muy fácil simular y administrar las dependencias externas. 

Considere una función que se ejecute solo al final del mes y ejecute algún código para limpiar las transacciones incorrectas. Sin interfaces, tendría que tomar medidas extremas, como cambiar el reloj de la computadora real para simular el fin de mes. Con una interfaz que proporciona la hora actual, solo pasa una estructura que establece el tiempo deseado para.

En lugar de importar hora y llamando directamente Ahora(), puedes pasar una interfaz con una Ahora() Método que en la producción será implementado por reenvío a Ahora(), pero durante la prueba será implementado por un objeto que devuelve un tiempo fijo para congelar el entorno de prueba.

Usando una interfaz Go

Usar una interfaz Go es completamente sencillo. Simplemente llamas a sus métodos como llamas a cualquier otra función. La gran diferencia es que no puede estar seguro de lo que sucederá porque puede haber diferentes implementaciones..

Implementando una interfaz Go

Las interfaces de Go se pueden implementar como métodos en estructuras. Considere la siguiente interfaz:

escriba Shape interface GetPerimeter () int GetArea () int 

Aquí hay dos implementaciones concretas de la interfaz Shape:

escriba Square struct side uint func (s * Square) GetPerimeter () uint return s.side * 4 func (s * Square) GetArea () uint return s.side * s.side type Rectangle struct width uint height uint func (r * Rectángulo) GetPerimeter () uint return (r.width + r.height) * 2 func (r * Rectangle) GetArea () uint return r.width * r.height 

El cuadrado y el rectángulo implementan los cálculos de manera diferente en función de sus campos y propiedades geométricas. El siguiente ejemplo de código muestra cómo rellenar una porción de la interfaz Shape con objetos concretos que implementan la interfaz, y luego iterar sobre la división e invocar la GetArea () Método de cada forma para calcular el área total de todas las formas..

func main () shapes: = [] Shape & Square side: 2, & Rectangle width: 3, height: 5 var totalArea uint para _, shape: = range shapes totalArea + = shape.GetArea ()  fmt.Println ("Total area:", totalArea) 

Implementación Base

En muchos lenguajes de programación, existe un concepto de una clase base que se puede usar para implementar la funcionalidad compartida utilizada por todas las subclases. Ir (con razón) prefiere la composición a la herencia. 

Puedes obtener un efecto similar incrustando una estructura. Vamos a definir una Cache Estructura que puede almacenar el valor de los cálculos anteriores. Cuando se recupera un valor del caso, también se imprime en la pantalla "hit de caché", y cuando el valor no está en el caso, imprime "falta de caché" y devuelve -1 (los valores válidos son enteros sin signo).

escriba Cache struct caché map [string] uint func (c * Cache) GetValue (nombre string) int value, ok: = c.cache [name] si está bien fmt.Println ("cache hit") return int ( valor) else fmt.Println ("cache miss") return -1 func (c * Cache) SetValue (cadena de nombre, valor uint) c.cache [nombre] = valor

Ahora, incrustaré este caché en las formas Cuadrado y Rectángulo. Tenga en cuenta que la implementación de GetPerimeter () y GetArea () ahora comprueba el caché primero y calcula el valor solo si no está en el caché.

escriba Square struct Cache side uint func (s * Square) GetPerimeter () uint value: = s.GetValue ("perimeter") si value == -1 value = int (s.side * 4) s.SetValue ("perimeter", uint (value)) return uint (value) func (s * Square) GetArea () uint value: = s.GetValue ("area") si value == -1 value = int ( s.side * s.side) s.SetValue ("area", uint (value)) return uint (value) type Rectangle struct Ancho de caché uint height uint func (r * Rectangle) GetPerimeter () uint value : = r.GetValue ("perimeter") si valor == -1 value = int (r.width + r.height) * 2 r.SetValue ("perimeter", uint (value)) return uint (value)  func (r * Rectangle) GetArea () uint value: = r.GetValue ("area") si value == -1 value = int (r.width * r.height) r.SetValue ("area", uint (valor)) devolver uint (valor)

Finalmente, el principal() La función calcula el área total dos veces para ver el efecto de caché.

func main () shapes: = [] Shape & Square Cache cache: make (map [string] uint), 2, & Rectangle Cache cache: make (map [string] uint), 3, 5  var totalArea uint para _, forma: = rango de formas totalArea + = forma.GetArea () fmt.Println ("Área total:", totalArea) totalArea = 0 para _, forma: = rango de formas totalArea + = shape.GetArea () fmt.Println ("Total area:", totalArea) 

Aquí está la salida:

caché fallar caché fallar área total: 19 caché de golpe caché golpeó área total: 19

Interfaz vs. Contrato

Las interfaces son excelentes, pero no garantizan que las estructuras que implementan la interfaz cumplan realmente la intención detrás de la interfaz. No hay manera en Ir para expresar esta intención. Todo lo que tienes que especificar es la firma de los métodos.. 

Para ir más allá de ese nivel básico, necesitas un contrato. Un contrato para un objeto especifica exactamente lo que hace cada método, qué efectos secundarios se realizan y cuál es el estado del objeto en cada momento. El contrato siempre existe. La única pregunta es si es explícito o implícito. En lo que respecta a las API externas, los contratos son críticos.

Conclusión

El modelo de programación Go fue diseñado alrededor de interfaces. Puedes programar en Go sin interfaces, pero te perderías sus muchos beneficios. Le recomiendo que aproveche al máximo las interfaces en sus aventuras de programación de Go.