jueves, 15 de agosto de 2013

Patrones comunes en el lenguaje Go! Parte 2 ( Estructuras especializadas de datos, Ocultamiento, tipos embebidos )

Hola a todos como están? Volvemos con los patrones comunes del lenguaje Go!, en este caso vamos a ver otros tres patrones que son: Estructuras especializadas de datos genéricas, Ocultamiento de la implementación, y tipos embebidos. Una vez que terminemos con esto vamos a continuar mirando cuestiones bastantes útiles como por ejemplo listas, manejos de string y otras cuestiones de mucha utilidad en cualquier lenguaje ;)




Estructuras especializadas de datos genéricos.


Un ejemplo de código antes de arrancar con la explicación:


func (s *stack) pushInt(v int64) {
if (s.isInteger) {
top := s.top.(*integerStackEntry)
if top.value == v {
top.count++
return
}
}
var e integerStackEntry
e.value = v
e.next = s.top
s.top = &e
s.isInteger = true
}


func (s *stack) Push(v interface{}) {
switch val := v.(type) {
case int64: s.pushInt(val)
case int: s.pushInt(int64(val))
default:
var e genericStackEntry
e.value = v
e.next = s.top
s.top = &e
s.isInteger = false
}
}



Supongamos que deseamos utilizar una pila donde poder meter y sacar de manera automática desde la última ubicación. Probablemente sería principalmente metiendo y sacando enteros, y a menudo metiendo el mismo entero varias veces seguidas. En este caso hay un gran potencial para la optimización: se puede tener una versión especializada de la entrada de la pila que almacena un entero y un conteo. Si queremos meter el mismo entero dos veces seguidas, sólo incrementamos la cuenta, ahorrandose los gastos generales de una asignación.
En el ejemplo se muestra un método que hace esto Push(). Este ahora utiliza dos tipos: uno para el caso genérico y uno para el caso con enteros.


Ejemplo de código:


type stackEntry interface {
pop() (interface{}, stackEntry)
}


type genericStackEntry struct {
next stackEntry
value interface{}
}


func (g genericStackEntry) pop() (interface{}, stackEntry) {
return g.value, g.next
}


type integerStackEntry struct {
value int64
count int
next stackEntry
}


func (i *integerStackEntry) pop() (interface{}, stackEntry) {
if (i.count > 0) {
i.count--
return i.value, i
}
return i.value, i.next
}


Cuando metemos un número entero, se utiliza una declaración de tipo “switch” para determinar el tipo, y comprobar si el último valor para a meter fue de un número entero y si tiene el mismo valor que el valor anterior. En este caso, sólo se incrementa el conteo. En este ejemplo se divide el trabajo especializado un poco entre la estructura de datos genéricas y los componentes especializados. Es posible que deseemos modificarlo para añadir un tryPush () a la interfaz stackEntry, que tratar de agregar el valor sin agregar una nueva entrada de la pila. Si no, podemos asignar una entrada nueva del mismo tipo.
Este patrón muestra una de las grandes ventajas de la articulación flexible que ofrece Go. La interfaz de la pila que utiliza una combinación de genericStackEntry y estructuras integerStackEntry para sus elementos es completamente compatible con la primera a la última sección, pero ahora es más eficiente para el almacenamiento de grandes secuencias de valores enteros idénticos. Los detalles en concreto de las dos estructuras que implementan las entradas a la pila están completamente ocultos. Podemos utilizar este mismo enfoque para implementar colecciones genéricas y luego especializar las a ellas para sus datos. Colecciones complejas en idiomas como Go suelen incorporar este tipo de comportamiento de auto-optimización.
Este es un ejemplo bastante simple y el ahorro en este caso particularmente no vale la pena. Si se implementa en una pila real, entonces una interfaz vacía es casi seguro que será mucho más rápida y utilizará menos memoria. El objetivo de este ejemplo es mostrar el patrón general, no la forma de escribir una pila eficiente en Go. Este patrón es muy útil en las estructuras de datos más complejas. Por ejemplo, la implementación que se encuentra en los tipos como “maps” utilizan una técnica similar, la generación de un valor hash basado en el tipo que tiene. Es posible que se desee hacer algo similar, proporcionando una función de hash para los tipos básicos, utilizando un hash cero por defecto, o por el método hash(), si existe.



Ocultamiento de la implementación


Ejemplo de código:


type Stack interface {
Push(interface{})
Pop() interface{}
}


func NewStack() Stack {
return &stack{}
}


Las interfaces de Go son exactamente lo que su nombre implica. Definen cómo se interactúa con un tipo, pero no la forma en que se implementa. Si los usuarios de su estructura no necesitan poder acceder a alguno de sus campos, entonces es una buena práctica exportar solo una interfaz donde expone los métodos públicos, en vez de la propia estructura. En el ejemplo se muestra una interfaz pública para las dos estructuras de pila que hemos definido ya en los anteriores patrones visto en este y el anterior post, junto con una función para construirla. Por convención, la función que crea la instancia concreta se llama NewSomething () (o NewAlgo() siendo Algo el nombre de la interfaz)
Esta no es la única manera de ocultar los detalles de implementación. Cualquier miembro de la estructura que no comienza con una letra mayúscula se oculta automáticamente, y sólo es accesible desde el interior del paquete en el que se declara. Como tal, las estructuras que hemos definido para implementar las pilas ya están ocultando los detalles de su aplicación: ninguno de sus campos es visible desde otros paquetes. El enfoque correcto a utilizar depende de cómo se espera que la gente utilice nuestras estructuras. Sólo exporta la interfaz proporciona la máxima flexibilidad, ya que puede cambiar por completo los detalles de la implementación sin alterar el código que lo utiliza. Podemos incluso aplicar varias estructuras diferentes optimizadas para diferentes casos de uso y devolver otras diferentes en función de su uso. Por otra parte, este enfoque impide a la gente asignar las instancias de su estructura en la pila, y no le permite usar el patrón de inicialización cero.
Aunque Go no admite la asignación de pila explícita, el compilador intentará asignar estructuras en la pila como un detalle de implementación si son de corta duración y no tiene las direcciones a tomar. Esto es muy rápido, ya que sólo requiere modificar el valor de un registro: las variables en la pila se asignan sólo con mover el puntero de pila. Si el objeto se devuelve como un puntero a través de una función constructora, el compilador no es capaz de hacer esto, y será necesario solicitar la memoria que es administrada por el recolector de basura. Para las estructuras de corta duración, esto puede ser una penalización de rendimiento significativa.


Tipos embebidos


Ejemplo de código:


type A struct {}
type B struct {}
func (_ A) Print() { fmt.Printf("Imprimir A\n") }
func (_ B) Print() { fmt.Printf("Imprimir B\n") }
func (a A) PrintA() { a.Print() }


type C struct {
A
*B
}


func main() {
var c C
c.B = &B{}
// heredado implícitamente
c.PrintA()
// No se permite la ambigüedad
// c.Print()
// explicitamente no-ambiguo
c.B.Print()
c.A.Print()
}


Go no admite subclases, pero es posible lograr algo similar a través de la forma limitada de delegación implícita que Go soporta. Si uno crea un campo sin nombre en una estructura de Go, entonces los métodos definidos por este tipo de campo se añade de forma implícita a la estructura circundante. En el ejemplo tenemos una estructura “C” que tiene dos campos sin nombre: campos con los tipos (A y B *), pero sin nombre. Tenemos que tener en cuenta que el receptor para cualquiera de estos métodos será el campo, no la estructura exterior. Esto significa que, incluso si el receptor es un puntero, no es posible para él acceder a cualquiera de los campos de la estructura exterior. Esto puede ser un poco incómodo. Por ejemplo, sería útil ser capaz de proporcionar una estructura de lista que podría ser añadida a cualquier estructura que necesita el comportamiento de la lista, la adición de un método Next() (o Siguiente()) devolvería el elemento siguiente de la lista, pero esto no es posible. En el ejemplo se muestra un problema que puede ocurrir cuando insertamos una estructura de esta manera: ¿Qué pasa cuando dos estructuras internas aplican los mismos métodos? Hay un montón de maneras de resolver este problema, incluyendo esquemas de prioridad, que pueden ser muy complicado, con varios niveles de anidación.
La solución de Go para que los programadores puedan especificar explícitamente lo que quieren decir. Por ejemplo llamando c.Print() en este ejemplo (la línea comentada) haría que el compilador rechace el programa: no se puede averiguar qué método Print() realmente buscamos, sin potencialmente introducir errores en el programa. Se podría extender este ejemplo mediante la adición de un método explícito de Print() en “C“que delega en uno de los campos, o implementado el método de alguna otra manera. Debemos tener en cuenta que este ejemplo utiliza tanto un puntero y un valor como campos, pero los métodos funcionan en ambos casos. Exactamente las mismas reglas para los métodos se aplican en este caso. El campo puntero añadirá métodos que toman un valor o un puntero, y el campo de valor va a añadir métodos que toman el valor.


Sitio Oficial:


Bueno eso fue todo por este post, espero que les allá gustado, hemos terminado con esta mínima visión de los patrones del lenguaje Go! En el próximo post sobre este lenguaje, seguramente estaremos viendo listas o algún tema similar , nos vemos.

Saludos a todos, Gabriel