Esta publicación se distribuye como un e-book gratuito y se encuentra en constante evolución. Puede acceder a la versión más actualizada desde la siguiente dirección:
http://www.designpatternsingo.com/
Los códigos fuentes de ejemplo de los patrones de diseño utilizados en este e-book se encuentran en el siguiente repositorio público:
https://github.com/danielspk/designpatternsingo
Asimismo, el código fuente de este e-book se encuentra en el siguiente repositorio público:
https://github.com/danielspk/designpatternsingoebook
Última actualización: 20 de Agosto de 2020 a las 20:55 hs.
Este trabajo tiene como objetivo discutir sobre la programación orientada a objetos en el lenguaje de programación Go, y como pueden implementarse los 23 Patrones de Diseño GoF.
La primera parte del trabajo está destinada a la presentación del lenguaje Go y a como se puede aplicar el paradigma orientado a objetos.
En la segunda parte se expone como pueden implementarse los 23 patrones de diseño GoF.
La tercera y última parte está destinada a las conclusiones generales y a los motivos que me llevaron a realizar esta publicación.
Figura: Logo oficial del Lenguaje Go 1
Figura: Gopher - Mascota del Lenguaje Go 1
“Go es un lenguaje de programación de código abierto que facilita la creación de software simple, confiable y eficiente”. [1]
“Go es expresivo, conciso, limpio y eficiente. Sus mecanismos de concurrencia facilitan la escritura de programas que aprovechan al máximo las máquinas multinúcleo, y de red, mientras que su novedoso sistema de tipo permite la construcción de programas flexibles y modulares. Go compila rápidamente el código de máquina y tiene la comodidad de la recolección de basura y el poder de la reflexión en tiempo de ejecución. Es un lenguaje compilado, rápido, de tipado estático, que se siente como un lenguaje interpretado de tipado dinámico”. [2]
Go fue creado en Google en el año 2007 por Robert Griesemer, Rob Pike, y Ken Thomson.
Figura: Gopher - Mascota del Lenguaje Go 2
Su lanzamiento oficial fue en noviembre del año 2009, pero su primera versión estable - 1.0 - recién se publicó en marzo de 2012.
Originalmente fue concebido para resolver problemas propios de la infraestructura de software de Google. Según palabras de unos de sus creadores Rob Pike, “Los objetivos del proyecto Go fueron eliminar la lentitud y la torpeza del desarrollo de software en Google y, por lo tanto, hacer que el proceso sea más productivo y escalable. El lenguaje fue diseñado por y para las personas que escriben, leen, depuran y mantienen sistemas de software grandes. Por lo tanto, el propósito de Go no fue investigar el diseño de un lenguaje de programación; sino mejorar el entorno de trabajo para sus diseñadores y sus compañeros de trabajo. Go tiene más que ver con la ingeniería del software que con la investigación en un lenguaje de programación” [17].
Entre los principales problemas de Google que motivaron el desarrollo de Go se pueden destacan:
Dado que la palabra Go
es parte del idioma ingles el lenguaje también es conocido como Golang.
Go está inspirado en la sintaxis de C como otros lenguajes: C++, C#, Java, PHP, Javascript, etc.
Su elección fue ser afín a la gran comunidad de desarrolladores de C++ de Google.
Por sus características suele clasificarse como un lenguaje compilado que tiene características de lenguajes interpretados.
Figura: Características del Lenguaje Go 3
Para Rob Pike: “Go es un intento de combinar la seguridad y el rendimiento de un lenguaje de tipado estático con la expresividad y la comodidad de un lenguaje interpretado de tipo dinámico.” [25]
Go se caracteriza por ser un lenguaje:
Se destaca también por su diseño minimalista y su facilidad para aprenderlo. A modo de comparación mientras otros lenguajes tienen muchas palabras reservadas, C++ 20 tiene 96, C# 7 tiene 78, Java 13 tiene 51; Go solo tiene 25.
Ejecutar código: https://play.golang.org/p/vhgR-fZxZv6
Para Mark Summerfield [36] “Go es bastante parecido a C en su espíritu, ya que es un lenguaje pequeño y eficiente con convenientes facilidades de bajo nivel, como punteros. Sin embargo, Go también ofrece muchas características asociadas con lenguajes de alto o muy alto nivel, como cadenas Unicode, potentes estructuras de datos integradas, duck typing, recolección de basura y soporte de concurrencia de alto nivel que utiliza comunicaciones en lugar de datos compartidos y bloqueos. Go también tiene una gran biblioteca estándar de amplio rango”.
Para Shiju Varghese [28] “El lenguaje de programación Go se puede describir simplemente en tres palabras: simple, mínimo y pragmático. El objetivo del diseño de Go es ser un lenguaje de programación simple, minimalista y expresivo que proporcione todas las características esenciales para crear sistemas de software confiables y eficientes. Cada idioma tiene su propio objetivo de diseño y una filosofía única. La simplicidad no se puede agregar más adelante en el idioma, por lo que debe ser construida con la simplicidad en mente. Go está diseñado para ser simple. Al combinar la simplicidad y el pragmatismo de Go, puede construir sistemas de software altamente eficientes con un mayor nivel de productividad”.
Para Karl Seguin [20] “Go tiene la naturaleza de simplificar la complejidad que hemos visto incluida en los lenguajes de programación en el último par de décadas mediante el uso de varios mecanismos”.
Para Caleb Doxsey [41] “Go es un lenguaje de programación de propósito general con características avanzadas y una sintaxis limpia. Debido a su amplia disponibilidad en una variedad de plataformas, su robusta biblioteca standard bien documentada y su enfoque en buenos principios de la ingeniería del software, Go es un gran lenguaje de programación para aprender”.
Go como todos los lenguajes de programación presenta ciertas controversias. Sus detractores por ejemplo manifiestan que el lenguaje carece de:
No obstante el equipo de diseño de Go no es ajeno a estas críticas, y permite que se propongan nuevas funcionalidades. Para esto se deben completar una serie de pasos que se encuentran documentados en el siguiente link: https://github.com/golang/proposal.
Go trata de respetar su filosofía de mantener un lenguaje extremadamente simple y rápido de compilar, por lo que la incorporación de nuevas características que pudieran afectar a uno de estos dos puntos debe poder justificarse claramente, y no debe existir forma alguna de poder llevar a cabo esa tarea con las características actuales del lenguaje. Por ejemplo estas son algunas respuestas que la documentación de Go da sobre la no existencia de excepciones:
“En Go, el manejo de errores es importante. El diseño y las convenciones del idioma lo alientan a verificar explícitamente si ocurren errores (a diferencia de la convención en otros idiomas de arrojar excepciones y, a veces, capturarlas). En algunos casos, esto hace que código de Go sea verboso, pero afortunadamente hay algunas técnicas que puede utilizar para minimizar el manejo de errores repetitivos.” [5]
“Creemos que acoplar excepciones a una estructura de control como en el try-catch-finally, da como resultado un código intrincado. También tiende a alentar a los programadores a etiquetar demasiados errores comunes, como no abrir un archivo, como excepcionales. Go toma un enfoque diferente. Para el manejo simple de errores, los retornos multi-valor de Go facilitan el reporte de un error sin sobrecargar el valor de retorno. Un tipo de error canónico, junto con otras características de Go, hace que el manejo de errores sea agradable, pero bastante diferente del de otros lenguajes.” [3]
Esta filosofía para algunos controvertida es la que creo en mi opinión que hace a Go tan interesante. En vez incorporar constantemente nuevas características y/o copiar otras de otros lenguajes de programación, Go intenta mantener un lenguaje simple, mínimo y conciso.
Los proverbios de Go invitan a los desarrolladores a reflexionar sobre la filosofía de Go y a la enseñanza sobre el lenguaje.
Se invita a los lectores a profundizar más sobre esta filosofía con base en la charla de Rob Pike en el Gopherfest del año 2015: “Go Proverbs”. [44]
A continuación se exponen solo algunos de estos proverbios:
Puede acceder al listado completo y actualizado en https://go-proverbs.github.io.
Figura: Gopher - Mascota del Lenguaje Go 1
Este apartado tratará de dar respuesta a la siguiente pregunta: ¿Es Go un lenguaje orientado a objetos?.
A diferencia de lenguajes explícitamente orientados a objetos como C++, Java, Smalltalk, y Python entre otros, en Go no existen las clases, ni los objetos, ni las excepciones, ni la herencia de clases.
Algunos, rápidamente podrían inferir que Go no es un lenguaje orientado a objetos. ¿Cómo puede existir un lenguaje orientado a objetos que no disponga de clases?. La pregunta que realmente debemos hacernos es: ¿Qué es la programación orientada a objetos?.
Los desarrolladores tenemos una tendencia natural a comparar las cosas. Entonces, por ejemplo, algunos podrían decir que dado que Java es académicamente reconocido como un lenguaje estrictamente orientado a objetos, y dado que ese lenguaje tiene entre otras características clases, objetos y herencia; como Go no las tiene, entonces no podría ser un lenguaje orientado a objetos.
¿Alguien alguna vez escuchó decir que Javascript es un lenguaje orientado a objetos?. Existe una gran discusión sobre si lo es o no lo es - a fin de cuentas en Javascript tampoco hay clases ni herencia de la forma como la que la hay en Java. No obstante Javascript suele ser considerado un lenguaje orientado a objetos. ¿Por qué?. Porque permite implementar ciertas características de la programación orientada a objetos.
Limitar el análisis a si un lenguaje es o no orientado a objetos por la sola existencia de la palabra reservada “class” sería absolutamente simplista e incorrecto. A modo de ejemplo, Objective-C define sus clases sin hacer uso de una palabra “class” y el propio lenguaje se define a sí mismo como proveedor de características orientadas a objetos.
Las clases no caracterizan a los lenguajes orientados a objetos. Un lenguaje soporta el paradigma orientado a objetos si respeta los pilares propios de la programación orientada a objetos. - se verán más adelante -.
Cada lenguaje es único y permite implementar el paradigma orientado a objetos de diversas maneras. Algunas comparaciones de ejemplo:
Como se puede apreciar lenguajes como Scala, Eiffel, C++ y Smalltalk son muy distintos entre sí, pero todos ellos respetan el paradigma orientado a objetos.
Al analizar a Go, debemos comparar qué características propias de la programación orientada a objetos se pueden implementar y no simplemente hacer una comparación de un lenguaje respecto de otro solamente por la falta o no de ciertos atributos o características individuales.
En Go no existen clases ni objetos. Existen tipos de datos
definidos por el usuario a los cuales se les pueden incorporar
comportamientos. En una analogía con una clase, las propiedades pudieran
ser los tipos de datos de una estructura, y los métodos las funciones o
comportamientos asociados al tipo de dato.
Ejemplo sobre una estructura:
Ejecutar código: https://play.golang.org/p/3uoR7qRs9eV
Ejemplo sobre un tipo de dato especializado:
Ejecutar código: https://play.golang.org/p/5WXUHGRBfn4
“La interfaz de un objeto no dice nada acerca de su implementación -
distintos objetos son libres de implementar las peticiones de forma
diferente -. Eso significa que dos objetos con implementaciones
completamente diferentes pueden tener interfaces idénticas”. [38]. Go cumple con esta característica de interfaces sin implementación. Lenguajes de programación tales como Visual Basic .Net o Kotlin definen sus interfaces de forma explícita. En Go las interfaces son implícitas ya que no existe ninguna palabra reservada o símbolo para tal fin (tal como implements
en Groovy o :
en C#). En Go
cualquier tipo de dato que implemente todos los métodos de una
interfaz, implícitamente la implementa. Este comportamiento es análogo
al de algunos lenguajes de tipado dinámico, como Python o Boo, donde a esto se lo conoce como Duck typing (“si camina como un pato y grazna como un pato, entonces debe ser un pato” [54]). En Go
el compilador chequea forzosamente que se si referencia a un tipo de
dato como una interface, este debe implementar todos sus comportamientos
sin excepción.
Ejemplo:
Ejecutar código: https://play.golang.org/p/MD6D893_1KB
Como se puede observar la función SaltarYCaminar() espera una variable de tipo Felino pero se le pasa una de tipo Leon. Como el tipo Leon implementar los métodos Caminar() y Saltar() implícitamente también es un Felino.
Los tres pilares de la programación orientada a objetos son la herencia, el encapsulamiento y el polimorfismo.
Como ya se dijo, en Go no existe la herencia, o al menos no la herencia de clases como se la conoce en otros lenguajes tales como Scala, Swift o Eiffel por ejemplo.
Los tipos de datos en Go permiten ampliar/modificar su comportamiento incrustando otros tipos dentro de él.
Cada estructura incorpora los tipos de datos y comportamientos de la/s estructura/s incrustada/s.
Ejemplo:
Ejecutar código: https://play.golang.org/p/oW83TcMCzHp
También es posible extender/reemplazar el comportamiento de la/s estructura/s incrustrada/s reescribiendo el/los comportamiento/s deseado/s.
Ejemplo:
Ejecutar código: https://play.golang.org/p/rBBwVyj9sjQ
En Go entonces se manifiesta la herencia de interfaz. - más información en el siguiente apartado de “Herencia / Composición”.
En Go no existen identificadores de privacidad tales como public, protected y private típicos de otros lenguajes de programación. Go encapsula tipos de datos y funciones a nivel de paquete en base a convenciones de nombres.
Todos
aquellos nombres que empiecen con mayúsculas serán accesibles
(visibles) desde otros paquetes. Por el contrario, aquellos que
comiencen con minúsculas serán privados.
Esta es la razón por la que en el código fuente de paquetes y bibliotecas de Go hay tipos de datos y funciones que pueden comenzar con mayúsculas o minúsculas.
Go es polimórfico. Gracias a la herencia de interfaces se pueden asignar referencias en forma polimórfica. - más información en el siguiente apartado de “Herencia / Composición”.
Ejemplo:
Ejecutar código: https://play.golang.org/p/yWlSrH_aXZg
En Go el polimorfismo se logra con la ayuda de interfaces.
Esta característica no es posible de replicar en Go dado que cada comportamiento asociado a un tipo de datos, solo puede ser invocado cuando exista un referencia a dicho tipo. Se puede emular esta característica usando el paradigma procedimental del lenguaje. Si bien no es un comportamiento puramente orientado a objetos, se pueden lograr similares resultados.
Ejemplo:
Según Gigi Sayfan [6] “Go es una extraña mezcla de ideas antiguas y nuevas.”, y “Muchas personas ni siquiera están seguras de si Go es un lenguaje orientado a objetos”, sin embargo para el “Go es un lenguaje de programación orientado a objetos de buena fe. Permite el modelado basado en objetos y promueve las mejores prácticas de usar interfaces en lugar de tipos concretos de jerarquías. Go tiene algunas elecciones sintácticas inusuales, pero el trabajo general con tipos, métodos e interfaces parece simple, ligero y natural. La incrustación no es muy pura, pero aparentemente estaba en juego el pragmatismo, y se proporcionó una incrustación en lugar de solo la composición por nombre”.
Para Junade Ali [39] “La programación orientada a objetos es más que solo clases y objetos; es un paradigma de programación basado alrededor de objetos (estructuras de datos) que contienen campos de datos y métodos. Es esencial entender esto; el uso de clases para organizar un grupo de métodos no relacionados no es orientación a objetos”.
Incluso la propia gente que desarrolla Go responde a esta pregunta [3] como “Si y no”.
Ya que existen otros lenguajes que permiten programar orientado a objetos, sin ser realmente orientados a objetos, puedo decir que “Go es un lenguaje no orientado a objetos que permite la programación orientada a objetos - aunque no de la forma tradicional”.
Como se indicó previamente en Go no existe el concepto de herencia de implementación típico de lenguajes orientados a objetos como Java, C++ y Smalltalk entre otros.
En Go existe otro concepto complementario que es la composición.
Tal como menciona Steve Francia [8] “Existen varios enfoques diferentes para definir relaciones entre objetos. Si bien difieren bastante entre sí, todos comparten un propósito común como mecanismo para la reutilización de código”:
La herencia se puede expresar de dos maneras: herencia de clases y herencia de interfaces.
“La herencia de clases define la implementación de un objeto en
términos de la implementación de otro objeto. En resumen, es un
mecanismo para compartir código y representación. Por el contrario, la
herencia de interfaces (o subtipado) describe cuándo se puede usar un objeto en el lugar de otro.” [38]
No
todos los lenguajes de programación implementan la herencia de la misma
manera. En algunos lenguajes la herencia de clases y la de interfaces
existen como un mismo mecanismo (Eiffel por ejemplo), mientras que en otros están separados (Java por ejemplo). Algunos solamente permiten heredar de un único objeto, esto se denomina herencia simple; mientras otros permiten heredar de varios objetos y a esto se lo denomina herencia múltiple.
Asimismo
los comportamientos y datos heredados pueden estar limitados al acceso
con el que el objeto padre los definió, esto se denomina visibilidad.
Se expresa a la herencia como una relación es-un/a.
La composición es una manera de definir objetos dentro de otros objetos. De esta forma un objeto puede adquirir los comportamientos y datos de los otros objetos por los que está compuesto.
Se expresa a la composición como una relación tiene-un/a.
Seguramente no haya una respuesta única. No obstante en el faq de la documentación oficial de Go responden a esta pregunta de la siguiente forma [3]:
“¿Por qué no hay herencia?:
La programación orientada a
objetos, al menos en los lenguajes más conocidos, implica demasiada
discusión sobre las relaciones entre tipos, relaciones que a menudo
podrían derivarse automáticamente. Go toma un enfoque diferente.
En lugar de requerir que el programador declare de antemano que dos tipos están relacionados, en Go
un tipo satisface automáticamente cualquier interfaz que especifique un
subconjunto de sus métodos. Además de reducir la administración (la palabra original es bookkeeping),
este enfoque tiene ventajas reales. Los tipos pueden satisfacer muchas
interfaces a la vez, sin las complejidades de la herencia múltiple
tradicional. Las interfaces pueden ser muy livianas - una interfaz con
uno o incluso cero métodos puede expresar un concepto útil. Las
interfaces se pueden agregar tardíamente si aparece una nueva idea o
para probarla - sin anotar los tipos originales. Debido a que no existen
relaciones explícitas entre los tipos y las interfaces, no hay una
jerarquía de tipos para administrar o discutir.
Es posible utilizar estas ideas para construir algo análogo a los type-safe de las pipes de Unix. Por ejemplo, vea cómo fmt.Fprintf permite la impresión formateada de cualquier salida, no solo de un archivo, o cómo el paquete bufio puede estar completamente separado de la E/S, o cómo el paquete image genera archivos de imágenes comprimidas. Todas estas ideas se derivan de una única interfaz (io.Writer) que representa un único método (Write). Y esto sólo está arañando la superficie. Las interfaces en Go tienen una profunda influencia sobre cómo se estructuran los programas.
Toma un tiempo acostumbrarse, pero este estilo implícito de dependencia de tipos es una de las cosas más productivas sobre Go.”
En la comunidad de práctica de desarrollo de software existen miles de afirmaciones poco fundadas del estilo ‘esto es mejor que aquello’.
Al incursionar en Go
y leer sobre por qué no existe la herencia me encontraba con frases del
estilo “la herencia es mala”, “la herencia simple no alcanza pero la
herencia múltiple es aún peor”, etc. En más de una oportunidad encontré
una referencia a un artículo de JavaWorld [14] donde el autor cuenta la siguiente anécdota:
“Una vez asistí a una reunión del grupo de usuarios de Java, donde James Gosling (el inventor de Java) fue el orador principal. Durante la memorable sesión de preguntas y respuestas, alguien le preguntó: ‘Si pudieras volver a hacer Java, ¿qué cambiarías?’ ‘Suprimiría las clases’, respondió. Después de que la risa se calmó, explicó que el verdadero problema no eran las clases en sí, sino la herencia de implementación (la relación extends). La herencia de interfaz (la relación implements) es preferible. Debe evitar la herencia de implementación siempre que sea posible.”.
Veamos un pequeño ejemplo en Java - ya que permite ambas formas - con algunos pros y contras - basado en el siguiente artículo [15]:
Pros:
Contras:
Pros:
Contras:
Como veremos la composición en Go se logra embebiendo tipos de datos unos dentro de otros:
Ejecutar código: https://play.golang.org/p/7W89468lRhC
Ejecutar código: https://play.golang.org/p/tZhaQwcvez9
Como se puede observar la función DarDeComer() espera una variable de tipo Primate pero se le pasa una de tipo Gorila. Como el tipo Gorila se compone de un Antropoide que implementa el método Alimentar() implícitamente también es un Primate.
Como se detallará más adelante, en la implementación de cada patrón de diseño, la falta de herencia será suplida de dos formas diferentes y/o complementarias:
Estrictamente hablando de programación orientada a objetos, la mayor dificultad encontrada es cuando una clase abstracta implementa un método concreto con comportamiento que llama a métodos abstractos también definidos en dicha clase abstracta que luego serán implementados en las clases hijas.
Para emular este comportamiento la estrategia utilizada en esta publicación será pasar como un argumento del método concreto de la clase abstracta una referencia de una interface que exponga cuáles serán los métodos abstractos que serán implementados por las clases hijas que implementen esa interface.
Veamos un ejemplo para entender la estrategia:
Ejecutar código: https://play.golang.org/p/NnEeU5Z4XWI
Go permite la composición y la herencia de interfaz. El uso de interfaces (es-un) y de la composición (tiene-un) posibilitan la reutilización de código en Go y la adopción de técnicas y de patrones orientados a objetos.
SOLID es un acrónimo introducido por Robert C. Martin en su libro “Agile Software Development, Principles, Patterns and Practices” [40] y hace referencia a los siguientes cinco principios:
El objetivo de aplicar estos principios es obtener sistemas orientados a objetos con código de mayor calidad, facilidad de mantenimiento y mejores oportunidades de reuso de código.
La primera observación respecto de este principio es que en Go no existen clases. Sin embargo, como vimos mediante la incorporación de comportamientos a tipos de datos, podemos llegar a un concepto equivalente.
Este principio hace foco en que un objeto debe tener únicamente una
responsabilidad encapsulada por la clase. Cuando se hace referencia a
una responsabilidad es para referirse a una razón para cambiar.
Mantener
una clase que tiene múltiples objetivos o responsabilidades es mucho
más complejo que una clase enfocada en una única responsabilidad.
El siguiente ejemplo no cumple con este principio porque otorga a una estructura dos responsabilidades bien diferenciadas: guardar en un archivo local y guardar en una base de datos.
Ejecutar código: https://play.golang.org/p/1d5JvLzQTEH
Un Documento debería saber cómo acceder al sistema de archivos local y a la vez como conectarse y operar contra una base de datos. Implementar ambas acciones en una misma estructura genera un alto acoplamiento.
Creando estructuras con responsabilidades bien definidas se puede mejorar el código de la siguiente manera:
Ejecutar código: https://play.golang.org/p/nxRPMtLbWP2
¿Qué pasa en Go: Gracias a la organización en paquetes que permite Go es posible crear estructuras, tipos, funciones y métodos empaquetados con propósitos claros y bien definidos.
Este principio propone que una entidad esté cerrada, lista para ser usada y estable en su calidad e interfaces, y al mismo tiempo abierta, es decir, que permita extender su comportamiento (pero sin modificar su código fuente).
Imaginemos que se requiere cambiar el comportamiento de la siguiente estructura únicamente en uno de sus métodos:
Ejecutar código: https://play.golang.org/p/FP80Iem9qqV
Podemos hacerlo de la siguiente forma:
Ejecutar código: https://play.golang.org/p/Sf1WkRugRN3
¿Qué pasa en Go: Gracias a la composición que permite Go es posible componer tipos simples en más complejos manteniendo la interfaz del tipo original.
Este principio propone que el contrato de una clase base debe ser honrado por sus clases derivadas para que instancias de las clases derivadas puedan reemplazar a instancias de la clase base.
Veamos el siguiente código:
Ejecutar código: https://play.golang.org/p/4jwdJ0NUjOe
La estructura Emision debe implementar dos comportamientos, ya que debe poder gestionar impresiones en HTML y JSON. Si a futuro se requiriera de otro tipo de impresión - xml por ejemplo - se debería modificar su código fuente.
La siguiente modificación permite intercambiar cualquier tipo de respuesta para su impresión:
Ejecutar código: https://play.golang.org/p/ZJ0iEXpWgt4
¿Qué pasa en Go: Al definir firmas de métodos a través de interfaces, y no mediante tipos concretos, es posible utilizar cualquier tipo que respete implícitamente la interfaz.
Este principio hace foco en como deben definirse las interfaces. Las mismas deben ser pequeñas y específicas.
Grandes y complejas interfaces obligan al cliente a implementar métodos que no necesita.
Veamos la siguiente interface:
Ejecutar código: https://play.golang.org/p/FHu4-lVUJ2D
Como puede verse la interface Boton obliga a implementar ambos comportamientos en sus clientes cuando es muy factible que no todos ellos deban implementar ambas opciones.
Una solución podría ser la siguiente:
Ejecutar código: https://play.golang.org/p/umwCkd0_eKQ
¿Qué pasa en Go: En Go puede aplicarse este concepto aislando el comportamiento requerido utilizando interfaces más pequeñas.
Este principio esta basado en reducir las dependencias entre los módulos del código para atacar el alto acoplamiento.
Veamos la siguiente ejemplo:
Ejecutar código: https://play.golang.org/p/ANqhCiBv3Zr
Como puede verse el tipo A depende del tipo B por lo que si el tipo B es modificado afectará al tipo A.
Una solución podría ser la siguiente:
Ejecutar código: https://play.golang.org/p/pHDUY3VCNTI
¿Qué pasa en Go: Componer tipos mediante interfaces, y no mediante tipos concretos, permite evitar una fuerte dependencia entre tipos.
Si bien el libro de Robert C. Martin [40] tiene más de una década y media y hace referencia a lenguajes propiamente orientados a objetos, vimos como también pueden aplicarse esos principios en Go.
Como se vio en el apartado anterior, el poder de la composición y de las interfaces implícitas le permiten a Go implementar buenas prácticas y conceptos propios de la programación orientada a objetos.
Figura: Gopher - Mascota del Lenguaje Go 1
Este apartado tratará sobre ¿Qué es un Patrón de Diseño? y ¿Qué es GoF?.
En el libro de Erich Gamma et al - Patrones de Diseño [38] - exponen la importancia y usos de los patrones de diseño en conceptos como:
Como se puede observar se utilizan términos como clases y objetos, ya que los patrones de diseño están enfocados al desarrollo orientado a objetos siendo los ejemplos de su libro implementados en C++.
En las siguientes secciones se detallará como es posible implementar dichos patrones de diseño en Go.
En esta publicación se utilizan los 23 patrones de diseño del libro
“Patrones de Diseño: Elementos de software orientado a objetos
reutilizable” [38] escrito por Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides.
El término GoF proviene de los autores del libro que son conocidos por la comunidad como Gang of Four (La Banda de los Cuatro).
En el libro Patrones de Diseño [38] los autores hacen la siguiente aclaración respecto de los lenguajes utilizados para documentar los 23 patrones: “Aunque los patrones describen diseños orientados a objetos, están basados en soluciones prácticas que han sido implementadas en los lenguajes de programación orientados a objetos más usuales, como Smalltalk y C++, en vez de mediante lenguajes procedimentales (Pascal, C, Ada) u otros lenguajes orientados a objetos más dinámicos (CLOS, Dylan, Self). Nosotros hemos elegido Smalltalk y C++ por una cuestión pragmática: nuestra experiencia diaria ha sido con estos lenguajes, y estos cada vez son más populares”. “La elección del lenguaje de programación es importante, ya que influye en el punto de vista. Nuestros patrones presuponen características de Smalltalk y C++, y esa elección determina lo que puede implementarse o no fácilmente. Si hubiéramos supuesto lenguajes procedimentales, tal vez, hubiéramos incluido patrones llamados ‘Herencia’, ‘Encapsulación’ y ‘Polimorfismo’. De manera similar, algunos de nuestros patrones están incluidos directamente en lenguajes orientados a objetos menos corrientes. CLOS, por ejemplo, tiene multi-métodos que reducen la necesidad de patrones como Visitor. De hecho, hay suficientes diferencias entre Smalltalk y C++ como para que algunos patrones puedan expresarse más fácilmente en un lenguaje que en otro (por ejemplo, el Iterator)”
También es importante entender que este trabajo intenta demostrar cómo pueden aplicarse los patrones de diseño en Go, aunque no es necesariamente imperativo su uso ni se lo promueve. Tal como exponen los autores, cada lenguaje tiene sus particularidades y en necesario entender el real problema a resolver, y no intentar adaptar formas de trabajo de un lenguaje a otro, sino entender esas particularidades que hacen diferente a un lenguaje respecto del otro para potenciar sus características.
A pesar de que parezca desalentador lo anteriormente dicho es importante remarcarlo. Un error muy común cuando aprendemos algo nuevo es querer utilizarlo en cualquier contexto, sin analizar realmente la conveniencia de su uso. Cuando uno aprende un patrón de diseño nuevo se ve tentado a utilizarlo. Los patrones de diseño resuelven problemas conocidos, y solo deben usarse si se está en presencia de un problema apropiado.
Un error que veo muy seguido es la adaptación de un patrón de un lenguaje a otro (como si se tratase de una traducción literal) cuando en realidad no se tienen en cuenta sus características específicas, que hacen a cada lenguaje de programación único, ni el objetivo que intenta resolver el patrón de diseño. Intento no caer yo mismo en este error.
Los patrones de diseño se organizan en tres familias de acuerdo a su propósito:
Los autores del libro Patrones de Diseño [38], también proponen otro criterio de clasificación denominado ámbito: “especifica si el patrón se aplica principalmente a clases o a objetos. Los patrones de clases se ocupan de relaciones entre las clases y sus subclases. Estas relaciones se establecen a través de la herencia, de modo que son relaciones estáticas - fijadas en tiempo de compilación -. Los patrones de objetos tratan con las relaciones entre objetos, que pueden cambiarse en tiempo de ejecución y son más dinámicas”.
Los patrones de diseño se catalogan de la siguiente forma:
Según su Propósito:
Según su Ámbito:
Creación | Estructurales | Comportamiento |
---|---|---|
Factory Method | Adapter | Interpreter |
Template Method |
Creación | Estructurales | Comportamiento |
---|---|---|
Abstract Factory | Adapter | Chain of Responsibility |
Builder | Bridge | Command |
Propotype | Composite | Iterator |
Singleton | Decorator | Mediator |
Facade | Memento | |
Flyweight | Observer | |
Proxy | State | |
Strategy | ||
Visitor |
Los autores del libro Patrones de Diseño [38] utilizan la siguiente plantilla para especificar un Patrón.
Sección | Detalle |
---|---|
Nombre del patrón y clasificación | El nombre del patrón transmite su esencia. El nombre es vital porque pasa a formar parte de nuestro vocabulario de diseño. Por ejemplo al decir Singleton todos entenderán de que se habla, sin necesidad de explicar el objetivo y los participantes del patrón. |
Propósito | Es una frase breve que responde a las siguientes cuestiones: ¿Qué hace este patrón de diseño?, ¿En qué se basa? ¿Cuál es el problema concreto de diseño que resuelve?. |
También conocido como | Son otros nombres, si existen, por los que también se conoce al patrón. |
Motivación | Un escenario que ilustra un problema de diseño y cómo las estructuras de clases y objetos del patrón resuelven el problema. |
Aplicabilidad | ¿En qué situaciones se puede aplicar el patrón de diseño? ¿Qué ejemplos hay de malos diseños que el patrón puede resolver? ¿Cómo se puede reconocer dichas situaciones?. |
Estructura | Una representación gráfica de las clases del patrón. |
Participantes | Las clases y objetos participantes en el patrón de diseño, junto con sus responsabilidades. |
Colaboradores | Cómo colaboran los participantes para llevar a cabo sus responsabilidades. |
Consecuencias | ¿Cómo consigue el patrón sus objetivos? ¿Cuáles son las ventajas, desventajas y resultados de usar el patrón?. |
Implementación | ¿Cuáles son las dificultades, trucos o técnicas que deberíamos tener en cuenta a la hora de aplicar el patrón? ¿Hay cuestiones específicas del lenguaje?. |
Código de ejemplo | Fragmentos de código que muestran cómo se puede implementar el patrón. |
Usos conocidos | Ejemplos del patrón en sistemas reales. |
Patrones relacionados | ¿Qué patrones de diseño están estrechamente relacionados con este? ¿Cuáles son las principales diferencias? ¿Con qué otros patrones debería usarse?. |
En esta publicación se utiliza la misma estructura pero con las siguientes observaciones:
En las explicaciones de cada patrón de diseño del libro Patrones de Diseño [38] utilizan constantemente las palabras objeto y clase. Parte de la comunidad asume que en Go la palabra objeto
es válida ya que se interpreta que es sinónimo de un tipo de dato con
comportamiento. Sin embargo, a fines de esta publicación cuando se
requiera hablar de un objeto utilizaré la frase “variable” (considero que se ajusta más al lenguaje).
Para la palabra clase no existe una terminología comparable, por lo que utilizaré simplemente la frase “tipo de dato” o la palabra “tipo”.
En cuanto a la palabra método, si bien es válida en Go dado que en definitiva los métodos son funciones que reciben un argumento receptor, también utilizaré la frase “comportamiento de un tipo”.
Dado que el libro Patrones de Diseño [38]
utiliza OMT en lugar de UML, se hará el mayor esfuerzo en respetar la
estructura original de cada patrón dado que cada lenguaje permite
implementaciones levemente distintas basándose en sus características
particulares.
Dicho esto se han observado pequeñas
alteraciones/concesiones en implementaciones donde se reemplazan clases
abstractas por interfaces y viceversa. Este tema es justamente una de
las principales diferencias que pueden encontrarse en las
implementaciones de un patrón de diseño GoF en Go, por lo que será dificultoso al lector diferenciar si una clase abstracta fue implementada como interface por a) una necesidad exclusiva dada las limitaciones del lenguaje Go en sus características orientadas a objetos; o b)
simplemente por una concesión válida entre los desarrolladores de otros
lenguajes. Lo invito en esos caso a buscar implementaciones de código
en otros lenguajes como Java o C#.
Figura: Gopher - Mascota del Lenguaje Go 1
Según el libro Patrones de Diseño [38] los patrones de comportamiento “tienen que ver con algoritmos y con la asignación de responsabilidades a objetos. Los patrones de comportamiento describen no solo patrones de clases y objetos, sino también patrones de comunicación entre ellos.”
Según el libro Patrones de Diseño [38] el patrón Strategy “define una familia de algoritmos, encapsula cada uno de ellos y los hace intercambiables. Permite que un algoritmo varíe independientemente de los clientes que lo usan”.
Policy
No se observan impedimentos y/o modificaciones de la estructura original del patrón para su implementación en Go.
En este ejemplo queremos definir tres estrategias concretas que pueden realizar distintas operaciones matemáticas. Cuando se crea el contexto se establece que estrategia deberá utilizar.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/OoMEcPgef7e
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/comportamiento/strategy
Según el libro Patrones de Diseño [38] el patrón Chain of Responsibility “evita acoplar el emisor de una petición a su receptor, dando a más de un objeto la posibilidad de responder a la petición. Encadena los objetos receptores y pasa la petición a través de la cadena hasta que es procesada por algún objeto”.
En este ejemplo se definen dos receptores distintos de mensajes. Uno para mensajes de alta prioridad y otro para mensajes de baja prioridad. El mensaje enviado por el cliente es transmitido a través de la cadena de receptores y cada receptor trata o no el mensaje de acuerdo a su prioridad.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/TnwdRltyBds
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/comportamiento/chainofresponsibility
Según el libro Patrones de Diseño [38] el patrón Command “encapsula una petición en un objeto, permitiendo así parametrizar a los clientes con diferentes peticiones, hacer cola o llevar registro de las peticiones, y poder deshacer las operaciones”.
Action, Transaction
No se observan impedimentos y/o modificaciones de la estructura original del patrón para su implementación en Go.
En este ejemplo queremos poder prender y apagar un televisor mediante la invocación de comandos mediante un control remoto.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/BRtWoVLF5nB
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/comportamiento/command
Según el libro Patrones de Diseño [38] el patrón Template Method “define en una operación el esqueleto de un algoritmo, delegando en las subclases algunos de sus pasos. Permite que las subclases redefinan ciertos pasos de un algoritmo sin cambiar su estructura”.
En este ejemplo queremos cumplir con una serie de pasos formales (método plantilla) para desplegar diferentes aplicaciones móviles.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/1J-MIDMaXi5
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/comportamiento/templatemethod
Implementación alternativa:
En esta alternativa no es necesario pasar la propia referencia del tipo concreto en el método Construir
. La construcción del tipo concreto se realiza componiéndolo con un tipo abstracto compuesto con el mismo tipo concreto.
Se puede probar la implementación alternativa del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/u5df18NIRI1
Según el libro Patrones de Diseño [38] el patrón Memento “representa y externaliza el estado interno de un objeto sin violar la encapsulación, de forma que este puede volver a dicho estado más tarde”.
Token
No se observan impedimentos y/o modificaciones de la estructura original del patrón para su implementación en Go.
En este ejemplo queremos que un editor de texto tenga la posibilidad de volver atrás su estado luego de una actualización de contenido.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/4o78qJhd-h2
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/comportamiento/memento
Según el libro Patrones de Diseño [38] el patrón Interpreter “dado un lenguaje, define una representación de su gramática junto con un intérprete que usa dicha representación para interpretar sentencias del lenguaje”.
En este ejemplo queremos interpretar distintas expresiones lógicas: AND y OR en base a palabras definidas en un contexto.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/zmXhDClx5k7
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/comportamiento/interpreter
Según el libro Patrones de Diseño [38] el patrón Iterator “proporciona un modo de acceder secuencialmente a los elementos de un objeto agregado sin exponer su representación interna”.
Cursor
En este ejemplo queremos recorrer las distintas estaciones de radio preseteadas de un estéreo de audio.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/qpY_F7wrd6u
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/comportamiento/iterator
Según el libro Patrones de Diseño [38] el patrón Visitor “representa una operación sobre los elementos de una estructura de objetos. Permite definir una nueva operación sin cambiar las clases de los elementos sobre los que opera”.
En este ejemplo queremos recrear un juego de rol en donde algunos personajes tengan superpoderes y otros armas de batalla.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/WSPGvlwREuQ
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/comportamiento/visitor
Según el libro Patrones de Diseño [38] el patrón State “permite que un objeto modifique su comportamiento cada vez que cambie su estado interno. Parecerá que cambia la clase del objeto”.
Objects for states (Estados como Objetos)
En este ejemplo queremos escribir texto en base al estado de una botonera de estilos, pudiendo ser estos estados “negrita” o “cursiva”.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/KsYfTyLBDVI
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/comportamiento/state
Según el libro Patrones de Diseño [38] el patrón Mediator “define un objeto que encapsula cómo interactúan una serie de objetos. Promueve un bajo acoplamiento al evitar que los objetos se refieran unos a otros explícitamente, y permite variar la interacción entre ellos de forma independiente”.
En este ejemplo queremos montar una sala de chat en donde los usuarios puedan comunicarse entre sí. La sala de chat actúa como mediador entre los usuarios.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/PWO1HBJYjPx
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/comportamiento/mediator
Según el libro Patrones de Diseño [38] el patrón Observer “define una dependencia de uno-a-muchos entre objetos, de forma que cuando un objeto cambie de estado se notifique y se actualicen automáticamente todos los objetos que depende de él”.
Dependents (Dependientes), Publish-subscribe (Publicar-Suscribir)
En este ejemplo queremos que postulantes a empleos sean notificados cuando se creen ofertas laborales.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/7CAEfYjM1lr
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/comportamiento/observer
Figura: Gopher - Mascota del Lenguaje Go 1
Según el libro Patrones de Diseño [38] los patrones de creación “abstraen el proceso de creación de instancias. Ayudan a hacer un sistema independiente de cómo se crean, se componen y se representan sus objetos. Un patrón de creación de clases usa la herencia para cambiar la clase de la instancia a crear, mientras que un patrón de creación de objetos delega la creación de la instancia en otro objeto.”
Esta definición de Gamma et al es un desafío de representar en Go ya que como hemos visto no existen ni clases, ni sus instancias, ni objetos y ni la herencia de clase. Sin embargo veremos como todo encaja en su lugar en cada patrón y cómo pueden implementarse en Go.
Go es un lenguaje que permite la programación concurrente y
los patrones de diseño creacionales asumen que se trabaja sobre un único
hilo de ejecución.
Dada la cantidad de estrategias y patrones de
concurrencia existentes, sería muy dificultoso detallar como pueden
verse afectadas las variables transmitidas por canales en desarrollos
concurrentes.
Sin embargo, se realizará una mención especial en la explicación del patrón Singleton
dado que como su propósito es crear una única variable de un tipo de
dato, será interesante ver que estrategia permite implementar el
lenguaje Go para cumplir con el propósito del patrón.
Según el libro Patrones de Diseño [38] el patrón Singleton “garantiza que una clase solo tenga una instancia, y proporciona un punto de acceso global a ella”.
sync
(https://golang.org/pkg/sync/) de Go. Concretamente el método Do
(https://golang.org/pkg/sync/#Once.Do) de la estructura Once
(https://golang.org/pkg/sync/#Once)
garantiza que la función pasada como parámetro puede ser ejecutada una
única vez mientras dure la ejecución del programa. Esta función será la
encargada de crear la única variable del tipo de dato Singleton.Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/Fae3WyvrdIf
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/creacionales/singleton
Según el libro Patrones de Diseño [38] el patrón Builder “separa la construcción de un objeto complejo de su representación, de forma que el mismo proceso de construcción pueda crear diferentes representaciones”.
En este ejemplo queremos que un local de comida pueda entregar distintos tipos de hamburguesas (simples y dobles) para lo que será necesario generar distintos constructores de hamburguesas.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/5dPp1a1Yaw_F
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/creacionales/builder
Según el libro Patrones de Diseño [38] el patrón Factory Method “define una interfaz para crear un objeto, pero deja que sean las subclases quienes decidan qué clase instanciar. Permite que una clase delegue en sus subclases la creación de objetos”.
Virtual Constructor (Constructor Virtual)
En este ejemplo queremos contratar personas con diferentes perfiles profesionales. A medida que los postulantes lleguen a la oficina de recursos humanos serán entrevistados (construidos) por diferentes reclutadores especializados.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/1szkQi-rVUf
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/creacionales/factorymethod
Según el libro Patrones de Diseño [38] el patrón Abstract Factory “proporciona una interfaz para crear familias de objetos relacionados o que dependen entre sí, sin especificar sus clases concretas”.
Kit
En este ejemplo queremos comprar distintos tipos de puertas de madera (madera o metal). Al realizar el pedido el local de venta debe encargar cada puerta a distintos fabricantes, ya que quien realiza la puerta de madera no la hace de metal y viceversa.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/8yy8vp4cDD5
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/creacionales/abstractfactory
Según el libro Patrones de Diseño [38] el patrón Prototype “especifica los tipos de objetos a crear por medio de una instancia prototípica, y crea nuevos objetos copiando dicho prototipo”.
No se observan impedimentos y/o modificaciones de la estructura original del patrón para su implementación en Go.
En este ejemplo queremos que un elemento químico sea capaz de clonarse a sí mismo indicando cuantas veces fue clonado.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/3OAK3-IzcTT
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/creacionales/prototype
Figura: Gopher - Mascota del Lenguaje Go 1
Según el libro Patrones de Diseño [38] los patrones estructurales “se ocupan de cómo se combinan las clases y los objetos para formar estructuras más grandes. Los patrones estructurales de clases hacen uso de la herencia para componer interfaces o implementaciones.”
Según el libro Patrones de Diseño [38] el patrón Composite “compone objetos en estructuras de árbol para representar jerarquías de parte-todo. Permite que los clientes traten de manera uniforme a los objetos individuales y a los compuestos”.
No se observan impedimentos y/o modificaciones de la estructura original del patrón para su implementación en Go.
En este ejemplo queremos acceder a los salarios de los empleados de una gerencia tanto de forma individual como grupal. De esta forma la compañía podría analizar el impacto de futuros bonos de productividad tanto para una gerencia completa o para alguno de sus empleados.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/BR_zwXpOD0O
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/estructurales/composite
Según el libro Patrones de Diseño [38] el patrón Adapter “convierte la interfaz de una clase en otra interfaz que es la que esperan los clientes. Permite que cooperen clases que de otra forma no podrían por tener interfaces incompatibles”.
Wrapper (Envoltorio)
No se observan impedimentos y/o modificaciones de la estructura original del patrón para su implementación en Go.
En este ejemplo queremos que un juego de RPG se pueda adaptar a un nuevo tipo de personaje (Magos) que no comparte las mismas características que los guerreros originales (Elfos). Para esto es necesario realizar un adaptador para que un Mago pueda atacar como un Elfo.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/60tlY8la04W
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/estructurales/adapter
Según el libro Patrones de Diseño [38] el patrón Bridge “desacopla una abstracción de su implementación, de modo que ambas puedan variar de forma independiente”.
Handle/Body (Manejador/Cuerpo)
En este ejemplo queremos desacoplar el protocolo de conexión a internet que pueden implementar distintos dispositivos.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/PnQdNHLsrSc
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/estructurales/bridge
Según el libro Patrones de Diseño [38] el patrón Proxy “proporciona un representante o sustituto de otro objeto para controlar el acceso a este”.
Surrogate (Sustituto)
No se observan impedimentos y/o modificaciones de la estructura original del patrón para su implementación en Go.
En este ejemplo queremos restringir los accesos a redes sociales en los navegadores de una escuela. Para esto realizaremos un proxy de protección.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/7JSOE4GYByc
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/estructurales/proxy
Según el libro Patrones de Diseño [38] el patrón Decorator “asigna responsabilidades adicionales a un objeto dinámicamente, proporcionando una alternativa flexible a la herencia para extender la funcionalidad”.
Wrapper (Envoltorio)
No se observan impedimentos y/o modificaciones de la estructura original del patrón para su implementación en Go.
En este ejemplo queremos agregarle dinámicamente ingredientes adicionales a un café que por defecto viene simple.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/62xDpf7XUv_m
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/estructurales/decorator
Según el libro Patrones de Diseño [38] el patrón Facade “proporciona una interfaz unificada para un conjunto de interfaces de un subsistema. Define una interfaz de alto nivel que hace que el subsistema sea más fácil de usar”.
No se observan impedimentos y/o modificaciones de la estructura original del patrón para su implementación en Go.
En este ejemplo queremos que un sistema de agencia de viajes pueda interactuar con otros subsistemas: buscador de hoteles y buscador de pasajes aéreos.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/JM-ZC-pmaxu
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/estructurales/facade
Según el libro Patrones de Diseño [38] el patrón Flyweight “usa comportamiento para permitir un gran número de objetos de grano fino de forma eficiente”.
No se observan impedimentos y/o modificaciones de la estructura original del patrón para su implementación en Go.
En este ejemplo queremos que nuestro sistema sea capaz de dibujar controles en la pantalla haciendo un uso eficiente de los recursos, ya que existen controles (los botones) que pueden ser reutilizados.
Implementación:
Se puede probar la implementación del patrón de la siguiente forma:
Ejecutar código: https://play.golang.org/p/o1TA4FcaAmD
Código de ejemplo: https://github.com/danielspk/designpatternsingo/tree/master/patrones/estructurales/flyweight
Go es un lenguaje que en muchos aspectos parece “retrasar” o da la sensación de “antiguo”. Esta visión puede ser correcta si se lo compara por ejemplo con la forma en la que programaríamos en Java. Esto es justamente lo que se debe evitar: “la comparación”. Go nace con una filosofía bien clara que todo desarrollador de Go debería conocer.
Dicho esto, tal como se pudo demostrar, es factible implementar los 23 Patrones de Diseño GoF en Go sin grandes modificaciones de las estructuras originales.
Su aplicación no es una simple traducción literal de un lenguaje a otro - como he visto muchas veces - sino que el uso de estos patrones realmente pueden aportar coherencia y valor al software desarrollado en Go.
Hemos visto que Go no es realmente un lenguaje orientado a objetos, sin embargo también se pudo demostrar que sus características permiten programar aplicando prácticas y patrones propios de la programación orientada a objetos.
Dicho esto, creo que es muy importante destacar que cada lenguaje de programación nace con un propósito definido, y que si bien las experiencias previas que tenemos como desarrolladores de software nos pueden facilitar el aprendizaje de nuevos lenguajes de programación, es vital comprender cuales son las recomendaciones, usos, estilos y características propias del lenguaje que estamos aprendiendo para no cometer el error común de querer hacer las cosas de igual manera como las haríamos en otros lenguajes.
Considero que Go es un excelente lenguaje para realizar trabajos concurrentes. Este tipo de programación hace que tengamos que repensar como podemos reutilizar soluciones ante problemas conocidos. Por tal motivo, y haciendo alusión a lo antes comentado, considero que un desarrollador de Go debería invertir más tiempo en aprender como aplicar patrones de concurrencia que patrones de diseño.
Estoy seguro de que todo lo visto en esta publicación es de valor para el desarrollador de Go, pero me gustaría cerrar esta conclusión con la siguiente recomendación:
Esta publicación fue motivada como trabajo final de mi Posgrado de Especialización en Ingeniería del Software que curse durante el año 2017 en la UCA - Pontificia Universidad Católica Argentina [53].
Figura: Logo de Pontificia Universidad Católica Argentina 1
A lo largo de los últimos 10 años vengo desarrollando software principalmente orientado a objetos. En uno de mis últimos proyectos debimos analizar un cambio en el lenguaje de programación que utilizábamos porque uno de los principales atributos de calidad que debía cumplir el nuevo software era la eficiencia; por esto era fundamental que el software fuera lo más rápido posible. Si bien es un análisis muy pobre el analizar que tan rápido puede ser un software únicamente mirando con que lenguaje de programación es desarrollado, esta era una oportunidad que como equipo teníamos para explorar nuevos lenguajes. Luego de varias pruebas y discusiones quedaron dos candidatos finales: Go (https://golang.org/) y Rust (https://www.rust-lang.org/en-US/). La paradoja de esta historia es que al final el proyecto se terminó realizando en Node.js (https://nodejs.org/en/). Las razones escapan a esta publicación, sin embargo una de ellas tuvo un fuerte impacto es esa decisión: “cómo transferir el conocimiento orientado a objetos que posee el grupo de desarrolladores a estos lenguajes que no son puramente orientados a objetos, o no de igual manera como lo conocen los desarrolladores de Java, C#, PHP, etc”. Anteriormente decía que la elección de Node.js fue una paradoja porque quien alguna vez ha desarrollado en Javascript sabrá las limitaciones que posee su orientación a objetos y de las grandes discusiones que hay en la comunidad sobre si Javascript es realmente un lenguaje orientado a objetos u orientado a prototipos. Esta es la razón principal de esta publicación, la de poder ayudar a un futuro equipo de tecnología a que puedan visualizar como es la Programación Orientada a Objetos en Go partiendo de los ya clásicos Patrones de Diseño GoF.
El trabajo presentado se denomina:
“Una perspectiva a la Programación Orientada a Objetos en Go en base a los Patrones de Diseño GoF”
El mismo se presenta como:
“El objetivo principal del presente trabajo será mostrar cómo pueden aplicarse los Patrones de Diseño GoF en un lenguaje de programación que no es completamente orientado a objetos. Para esto se utilizará como referencia el lenguaje de programación Go. Su propósito principal será servir de material de referencia para desarrolladores Go que deseen aplicar los patrones de Diseño GoF. Se analizará la viabilidad, los ajustes, y las adaptaciones necesarias para implementar los Patrones de Diseño en base a las características del lenguaje de programación Go. Para esto, previamente se abordarán y explicarán los atributos orientados a objetos que posee el lenguaje de programación Go. Adicionalmente el trabajo se publicará como e-book online mediante la realización de una página web, junto a ejemplos auto ejecutables y un repositorio público con todo el código fuente, ejemplos y diagramas desarrollados.”
Al momento de tomar la decisión sobre cuál de los dos lenguajes antes mencionados (Go, Rust) iba a basar mi trabajo, decidí hacer una pequeño análisis sobre la popularidad de ambos lenguajes en los últimos años. Para esto tome como referencia el índice de popularidad de los lenguajes de programación [51] de la Empresa Tiobe (https://www.tiobe.com).
Tiobe es una empresa especializada en la evaluación y el seguimiento de la calidad del software que publica mensualmente un índice [51] de popularidad de lenguajes de programación. Este índice es conformado en base a la cantidad de resultados que arrojan varios motores de búsqueda. Los resultados deben cumplir ciertos requerimientos para calificar para el índice, por ejemplo el lenguaje de programación debe tener su propia entrada en Wikipedia, ser completo (poder simular una máquina de Turing) y tener al menor 5.000 visitas en búsquedas aplicadas desde Google. La empresa también utiliza filtros para evitar falsos positivos y otras reglas para valorar la calificación del resultado.
El proceso de como Tiobe confecciona el índice se encuentra bien detallado en el siguiente link: https://www.tiobe.com/tiobe-index/programming-languages-definition/
Basándome en el índice antes mencionado realicé una comparación entre ambos lenguajes y Go, tanto históricamente como actualmente, tiene mayor popularidad respecto de Rust.
Como se puede apreciar en la siguiente gráfica (Mayo/2018) Go obtuvo su mejor ubicación dentro del ranking en Julio de 2017 al ubicarse entre los primeros 10 lenguajes de programación. Desde mediados de 2016 a mediados de 2017 tuvo su mayor pico de popularidad, con un marcado descenso actualmente.
Figura: Evolución de la popularidad de Go 2
Actualmente (Mayo/2018) Go se ubica en la 14ª posición:
Figura: Ranking de popularidad de Go 3
Por el contrario Rust se ubica actualmente (Mayo/2018) en la 91ª posición en una clara caída dentro del ranking. Si bien por su posición actual sus estadísticas ya no se publican dentro del índice Tiobe, al momento de su evaluación estaba dentro del ranking de los 20ª a 50ª.
Existen otros sitios especializados en medir la popularidad de los lenguajes de programación como http://pypl.github.io/PYPL.html, y http://redmonk.com/sogrady/category/programming-languages/, pero Tiobe es a mi entender el que más criterios de evaluación y filtros detalla para su ranking.
En conclusión, el historial de popularidad de Go respecto de Rust fue lo que definió su selección para la publicación de este trabajo.
Antes de formalizar la idea final de esta publicación me definí dos objetivos para evaluar la factibilidad de su realización:
Mi primer objetivo fue muy fácil de corroborar. Si bien la inexistencia de “clases” en Go es un condicionante en la forma en que se expresan y documentan los patrones de diseño GoF; la semántica de tipos de datos que implementan comportamientos fue, a mi entender, lo suficientemente análoga a las clases tradicionales. Semánticamente los patrones son implementables en Go - con su sintaxis particular -.
El segundo objetivo fue más difícil de llevar a cabo: ¿Qué significa que la implementación de un patrón de diseño GoF aporte valor al software?. La mejor manera que encontré de dar respuesta a esta pregunta fue demostrar todo lo contrario: ¿Cómo una implementación semántica de un patrón de diseño GoF no aporta valor alguno al software?.
El siguiente código de Javascript ES5 implementa el patrón de comportamiento Strategy:
Semánticamente la aplicación del patrón es correcta. El contexto es el Transportista y las estrategias son las Empresas. Sin embargo esta implementación es exclusivamente semántica, porque el lenguaje:
Como se puede ver la programación orientada a objetos en Javascript ES5 es muy limitada, y si bien semánticamente puede implementarse los patrones de diseño GoF, aportan valor al software sólo en cuanto a su funcionamiento final pero no así respecto de la reutilización de código.
En contrapartida, en Go, la aplicación de patrones de diseño GoF aportan valor pleno al software como en otros lenguajes de programación orientados a objetos, ya que el lenguaje tiene características y comportamientos comparables.
En conclusión mis objetivos iniciales fueron cumplimentados y los siguientes pasos fueron:
Para esta publicación se priorizó la utilización de herramientas que cumplieran alguna de las siguientes premisas:
Las herramientas utilizadas fueron:
La motivación de la elección de estas, y no otras herramientas, fue la siguiente:
Quisiera concluir agradeciendo a:
A continuación se exponen los recursos de interés que fueron utilizados como referencia para el desarrollo de esta publicación.
[1] - Web oficial
Fuente: https://golang.org
[2] - Documentación oficial
Fuente: https://golang.org/doc
[3] - Preguntas frecuentes oficiales
Fuente: https://golang.org/doc/faq
[4] - Consola interactiva
Fuente: https://play.golang.org
[5] - Blog oficial
Fuente: https://blog.golang.org
[6] - Let’s Go: Object-Oriented Programming in Golang
Fuente: https://code.tutsplus.com/tutorials/lets-go-object-oriented-programming-in-golang--cms-26540
[7] - SOLID Go Design
Fuente: https://dave.cheney.net/2016/08/20/solid-go-design
[8] - Is Go An Object Oriented Language?
Fuente: http://spf13.com/post/is-go-object-oriented
[9] - Object Orientation In Go
Fuente: https://katcipis.github.io/blog/object-orientation-go
[10] - Design Patterns for Humans!
Fuente: https://github.com/kamranahmedse/design-patterns-for-humans
[11] - Java Design Patterns - Example Tutorial
Fuente: https://www.journaldev.com/1827/java-design-patterns-example-tutorial
[12] - Design patterns implemented in Java
Fuente: http://java-design-patterns.com
[13] - Design Patterns in Java Tutorial
Fuente: https://www.tutorialspoint.com/design_pattern
[14] - Why extends is evil
Fuente: https://www.javaworld.com/article/2073649/core-java/why-extends-is-evil.html
[15] - Inheritance vs Composition
Fuente: https://www.techjini.com/blog/inheritance-vs-composition
[16] - Go is not good
Fuente: https://github.com/ksimka/go-is-not-good
[17] - Go at Google: Language Design in the Service of Software Engineering
Fuente: https://talks.golang.org/2012/splash.article
[18] - Publicaciones académicas
Fuente: https://github.com/golang/go/wiki/ResearchPapers
[19] - GoHotDraw: Evaluating the Go Programming Language with Design Patterns - Frank Schmager, Nicholas Cameron, James Noble
Fuente: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.188.5524&rep=rep1&type=pdf
[20] - Karl Seguin, “The Litte Go Book”, 2014
Fuente: https://github.com/karlseguin/the-little-go-book
[21] - Hemant Jain, “Data Structures & Algorithms In Go” - WOW, 2017
Fuente: https://www.amazon.com/Data-Structures-Algorithms-Hemant-Jain-ebook/dp/B075TBM9KS
[22] - Matt Aimoneaatti, “Go Bootcamp” - 2014
Fuente: http://www.golangbootcamp.com
[23] - Aaron Torres, “Go Cookbook” - Packt, 2017
Fuente: https://www.packtpub.com/application-development/go-cookbook
[24] - Mario Castro Contreras, “Go Design Patterns” - Packt, 2017
Fuente: https://www.packtpub.com/application-development/go-design-patterns
[25] - Vladimir Vivien, Mario Castro Contreras, Mat Ryer, “Go: Design Patterns for Real-World Projects” - Packt, 2017
Fuente: https://www.packtpub.com/application-development/go-design-patterns-real-world-projects
[26] - Matt Butcher, Matt Farina, “Go In Practice” - Manning, 2016
Fuente: https://www.manning.com/books/go-in-practice
[27] - Mat Ryer, “Go Programming Blueprints Second Edition” - Packt, 2016
Fuente: https://www.packtpub.com/application-development/go-programming-blueprints-second-edition
[28] - Shiju Varghese, “Go Recipes” - Apress, 2016
Fuente: https://www.apress.com/br/book/9781484211892
[29] - Mihalis Tsoukalos, “Go Systems Programming” - Packt, 2017
Fuente: https://www.packtpub.com/networking-and-servers/go-systems-programming
[30] - Sau Sheong Chang, “Go Web Programming” - Manning, 2016
Fuente: https://www.manning.com/books/go-web-programming
[31] - Vladimir Vivien, “Learning Go Programming” - Packt, 2016
Fuente: https://www.packtpub.com/application-development/learning-go-programming
[32] - Nathan Zozyra, “Learning Go Web Development” - Packt, 2016
Fuente: https://www.packtpub.com/web-development/learning-go-web-development
[33] - Daniel Whitenack, “Machine Learning With Go” - Packt, 2017
Fuente: https://www.packtpub.com/big-data-and-business-intelligence/machine-learning-go
[34] - Jan Newmarch, “Network Programming with Go” - Apress, 2017
Fuente: https://www.apress.com/de/book/9781484226919
[35] - Nathan Zozyra, Mat Ryer, “Go: Building Web Applications” - Packt, 2016
Fuente: https://www.packtpub.com/application-development/go-building-web-applications
[36] - Mark Summerfield, “Programming in Go” - Addison-Wesley Professional, 2012
Fuente: https://www.amazon.com/Programming-Go-Creating-Applications-Developers/dp/0321774639
[37] - Thorsten Ball, “Writing an Interpreter in Go” - 2016
Fuente: https://interpreterbook.com
[38] - Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, “Patrones de Diseño” - Addison Wesley, 2002
Fuente: https://www.amazon.es/Patrones-dise%C3%B1o-Erich-Gamma/dp/8478290591
[39] - Junade Ali, “Mastering PHP Design Patterns” - Packt, 2016
Fuente: https://www.packtpub.com/application-development/mastering-php-design-patterns
[40] - Robert C. Martin, “Agile Software Development, Principles, Patterns, and Practices” - Alan Apt Series, 2002
Fuente: https://www.amazon.es/Software-Development-Principles-Patterns-Practices/dp/0135974445
[41] - Caleb Doxsey, “Introducing Go” - O’Reilly, 2016
Fuente: http://shop.oreilly.com/product/0636920046516.do
[42] - Go for Javaneros
Fuente: https://talks.golang.org/2014/go4java.slide#1
[43] - Rob Pike - Simplicity is Complicated
Fuente: https://www.youtube.com/watch?v=rFejpH_tAHM
[44] - Rob Pike - Go Proverbs
Fuente: https://www.youtube.com/watch?v=PAAkCSZUG1c
[45] - Francesc Campoy - Google Understanding Go Interfaces
Fuente: https://www.youtube.com/watch?v=F4wUrj6pmSI
[46] - Dave Cheney - SOLID Go Design
Fuente: https://www.youtube.com/watch?v=zzAdEt3xZ1M
[47] - William Kennedy - Golang OOP - Composition in Go
Fuente: https://www.youtube.com/watch?v=194blNHDdd0
[48] - Librerías, paquetes y framewoks de Go
Fuente: https://github.com/avelino/awesome-go
[49] - Gophers oficiales
Fuente: https://golang.org/doc/gopher
[50] - Gophers libres
Fuente: https://github.com/golang-samples/gopher-vector
[51] - Índice Tiobe
Fuente: https://www.tiobe.com/tiobe-index
[52] - Logos oficiales de Go
Fuente: https://golang.org/s/logos
[53] - UCA - Pontificia Universidad Católica Argentina
Fuente: http://uca.edu.ar
[54] - Python - Glosario
Fuente: https://docs.python.org/3/glossary.html?highlight=duck#term-duck-typing
Las clases propias de la programación orientada a objetos se definirán como tipos de datos o tipos.
Los objetos propios de la programación orientada a objetos se definirán como variables.
Los métodos de clases propios de la programación orientada a objetos se definirán como comportamientos de un tipo o simplemente como método (de tipo).
Las propiedades de clases propias de la programación orientada a objetos se definirán como atributos de una estructura.
1Fuente: https://golang.org/s/logos↩
1Fuente: https://golang.org/doc/gopher↩
2Fuente: https://github.com/golang-samples/gopher-vector↩
3Fuente: Vladimir Vivien, Mario Castro Contreras, Mat Ryer, “Go: Design Patterns for Real-World Projects” - Packt, 2017↩
1Fuente: https://golang.org/doc/gopher↩
1Fuente: https://golang.org/doc/gopher↩
1Fuente: https://golang.org/doc/gopher↩
1Fuente: https://golang.org/doc/gopher↩
1Fuente: https://golang.org/doc/gopher↩
1Fuente: http://uca.edu.ar↩
2Fuente: https://www.tiobe.com/tiobe-index/go/↩
3Fuente: https://www.tiobe.com/tiobe-index↩