type structs inicializar golang create struct interface go

structs - instance struct go



Ir a campos de interfaz (1)

Definitivamente es un truco ingenioso, y funciona siempre y cuando estés tranquilo dando acceso a esos campos como parte de tu API. La alternativa que consideraría es mantener la configuración de estructura / interface incrustable, pero definiendo la interfaz en términos de getters y setters.

Ocultar las propiedades detrás de getters y setters le da cierta flexibilidad adicional para hacer cambios compatibles hacia atrás más tarde. Supongamos que algún día quiere cambiar Person para almacenar no solo un campo de "nombre" sino primero / medio / último / prefijo; si tiene los métodos Name() string y SetName(string) , puede mantener contentos a los usuarios existentes de la interfaz de la Person al agregar nuevos métodos más detallados. O puede querer marcar un objeto respaldado por una base de datos como "sucio" cuando tiene cambios no guardados; puedes hacerlo cuando las actualizaciones de datos pasan por los métodos SetFoo() .

Entonces: con getters / setters, puede cambiar los campos de estructura mientras mantiene una API compatible, y agregar lógica alrededor de la propiedad get / sets ya que nadie puede hacer p.Name = "bob" sin pasar por su código.

Esa flexibilidad es más relevante cuando tu tipo hace algo más complicado. Si tiene una PersonCollection , podría estar internamente respaldada por un sql.Rows , a []*Person , a []uint de ID de base de datos, o lo que sea. Usando la interfaz correcta, puede evitar que las personas que llaman io.Reader qué se trata, de la misma manera que io.Reader hace que las conexiones de red y los archivos se parezcan.

Una cosa específica: las interface en Go tienen la peculiar propiedad de que puedes implementar una sin importar el paquete que la define; eso puede ayudarlo a evitar importaciones cíclicas . Si su interfaz devuelve una *Person , en lugar de sólo cadenas o lo que sea, todos los PersonProviders deben importar el paquete donde se define Person . Eso puede estar bien o incluso ser inevitable; es solo una consecuencia para saber.

Dicho todo esto, no hay una convención de Go que tenga que ocultar todos sus datos. (Esta es una buena diferencia de, digamos, C ++). El stdlib hace cosas como permitirle inicializar un http.Server con su configuración y promete que se puede usar un bytes.Buffer cero bytes.Buffer . Está bien hacer tus propias cosas así, y, de hecho, no creo que tengas que hacer una abstracción prematura si funciona la versión más concreta y que expone los datos. Solo se trata de ser consciente de las compensaciones.

Estoy familiarizado con el hecho de que, en Go, las interfaces definen la funcionalidad, en lugar de los datos. Pones un conjunto de métodos en una interfaz, pero no puedes especificar ningún campo que se requiera en cualquier elemento que implemente esa interfaz.

Por ejemplo:

// Interface type Giver interface { Give() int64 } // One implementation type FiveGiver struct {} func (fg *FiveGiver) Give() int64 { return 5 } // Another implementation type VarGiver struct { number int64 } func (vg *VarGiver) Give() int64 { return vg.number }

Ahora podemos usar la interfaz y sus implementaciones:

// A function that uses the interface func GetSomething(aGiver Giver) { fmt.Println("The Giver gives: ", aGiver.Give()) } // Bring it all together func main() { fg := &FiveGiver{} vg := &VarGiver{3} GetSomething(fg) GetSomething(vg) } /* Resulting output: 5 3 */

Ahora, lo que no puedes hacer es algo como esto:

type Person interface { Name string Age int64 } type Bob struct implements Person { // Not Go syntax! ... } func PrintName(aPerson Person) { fmt.Println("Person''s name is: ", aPerson.Name) } func main() { b := &Bob{"Bob", 23} PrintName(b) }

Sin embargo, después de jugar con interfaces y estructuras incorporadas, descubrí una forma de hacerlo, después de una moda:

type PersonProvider interface { GetPerson() *Person } type Person struct { Name string Age int64 } func (p *Person) GetPerson() *Person { return p } type Bob struct { FavoriteNumber int64 Person }

Debido a la estructura integrada, Bob tiene todo lo que tiene la persona. También implementa la interfaz PersonProvider, por lo que podemos pasar a Bob a funciones diseñadas para usar esa interfaz.

func DoBirthday(pp PersonProvider) { pers := pp.GetPerson() pers.Age += 1 } func SayHi(pp PersonProvider) { fmt.Printf("Hello, %v!/r", pp.GetPerson().Name) } func main() { b := &Bob{ 5, Person{"Bob", 23}, } DoBirthday(b) SayHi(b) fmt.Printf("You''re %v years old now!", b.Age) }

Aquí hay un Go Playground que muestra el código anterior.

Con este método, puedo crear una interfaz que defina datos en lugar de comportamientos, y que cualquier estructura pueda implementar mediante la incorporación de esos datos. Puede definir funciones que interactúan explícitamente con los datos incrustados y desconocen la naturaleza de la estructura externa. ¡Y todo se comprueba en tiempo de compilación! (La única forma de que puedas PersonProvider , que yo pueda ver, sería incorporar la interfaz PersonProvider en Bob , en lugar de una Person concreta. Se compilaría y fallaría en tiempo de ejecución).

Ahora, aquí está mi pregunta: ¿es este un buen truco, o debería hacerlo de otra manera?