One place for hosting & domains

      métodos

      Definindo métodos em Go


      Introdução

      As funções permitem que você organize a lógica em procedimentos repetíveis que possam usar diferentes argumentos sempre que forem executados. Durante o processo de definição das funções, com frequência você verá que várias funções podem operar na mesma parte dos dados a cada vez. O Go reconhece esse padrão e permite que você defina funções especiais, chamadas de methods (métodos), cujo objetivo é operar em instâncias de um tipo específico, chamado de receiver (receptor). A adição dos métodos aos tipos permite que você comunique não apenas sobre do que se tratam os dados, mas também como esses dados devem ser usados.

      Definindo um método

      A sintaxe para definir um método é similar à sintaxe para definir uma função. A única diferença é a adição de um parâmetro extra após a palavra-chave func, para especificar o receptor do método. O receptor é uma declaração do tipo que você deseja para definir o método. O exemplo a seguir define um método em um tipo struct:

      package main
      
      import "fmt"
      
      type Creature struct {
          Name     string
          Greeting string
      }
      
      func (c Creature) Greet() {
          fmt.Printf("%s says %s", c.Name, c.Greeting)
      }
      
      func main() {
          sammy := Creature{
              Name:     "Sammy",
              Greeting: "Hello!",
          }
          Creature.Greet(sammy)
      }
      

      Se executar este código, o resultado será:

      Output

      Sammy says Hello!

      Criamos um struct chamado Creature, com os campos da string para Name e Greeting. Este Creature tem um método único definido, o Greet. Dentro da declaração do receptor, atribuímos a instância do Creature à variável c para que pudéssemos referir-nos aos campos do Creature, à medida que agrupamos a mensagem de saudação no fmt.Printf.

      Em outras linguagens,normalmente, referimo-nos ao receptor das invocações de métodos por meio de uma palavra-chave (p. ex., this ou self). O Go considera o receptor como uma variável como qualquer outra. Assim, você pode dar a ele o nome que quiser. O estilo preferido pela comunidade para esse parâmetro é a versão do tipo de receptor com o primeiro caractere escrito em letra minúscula. Neste exemplo, usamos o c porque o tipo de receptor era o Creature.

      Dentro do corpo do main, criamos uma instância de Creature e especificamos os valores para seus campos Name e Greetings. Aqui, invocamos o método Greet, juntando o nome do tipo e o nome do método com um ., fornecendo a instância de Creature como o primeiro argumento.

      A linguagem Go oferece outras maneiras mais convenientes de chamar os métodos em instâncias de um struct, como mostramos neste exemplo:

      package main
      
      import "fmt"
      
      type Creature struct {
          Name     string
          Greeting string
      }
      
      func (c Creature) Greet() {
          fmt.Printf("%s says %s", c.Name, c.Greeting)
      }
      
      func main() {
          sammy := Creature{
              Name:     "Sammy",
              Greeting: "Hello!",
          }
          sammy.Greet()
      }
      

      Se executar isso, o resultado será o mesmo que o do exemplo anterior:

      Output

      Sammy says Hello!

      Esse exemplo é idêntico ao anterior. Porém, desta vez usamos dot notation para invocar o método Greet usando o Creature armazenado na variável sammy como o receptor. Esta é uma notação abreviada para a invocação da função no primeiro exemplo. A preferência que a biblioteca padrão e a comunidade Go têm por esse estilo é tanta que raramente veremos o estilo de invocação de função mostrado anteriormente.

      O próximo exemplo mostra um motivo pelo qual a notação de ponto é a mais prevalente:

      package main
      
      import "fmt"
      
      type Creature struct {
          Name     string
          Greeting string
      }
      
      func (c Creature) Greet() Creature {
          fmt.Printf("%s says %s!n", c.Name, c.Greeting)
          return c
      }
      
      func (c Creature) SayGoodbye(name string) {
          fmt.Println("Farewell", name, "!")
      }
      
      func main() {
          sammy := Creature{
              Name:     "Sammy",
              Greeting: "Hello!",
          }
          sammy.Greet().SayGoodbye("gophers")
      
          Creature.SayGoodbye(Creature.Greet(sammy), "gophers")
      }
      

      Se executar esse código, o resultado ficará parecido com este:

      Output

      Sammy says Hello!! Farewell gophers ! Sammy says Hello!! Farewell gophers !

      Nós modificamos os exemplos anteriores para introduzir um outro método chamado SayGoodbye e mudamos o Greet para retornar um Creature para que possamos invocar outros métodos naquela instância. No corpo do main, chamamos os métodos Greet e SayGoodbye na variável sammy, primeiro usando a notação de ponto e, depois, utilizando o estilo de invocação de função.

      Ambos os estilos produzem os mesmos resultados, mas o exemplo que usa a notação de ponto é muito mais legível. A cadeia de pontos também nos diz a sequência na qual os métodos serão invocados, onde o estilo funcional inverte essa sequência. A adição de um parâmetro à chamada SayGoodbye torna confusa a ordem das chamadas de método. A clareza da notação de ponto é o motivo deste ser o estilo preferido para invocar os métodos no Go: seja na biblioteca padrão ou entre pacotes de terceiros, você o encontrará por todo o ecossistema Go.

      Definir métodos sobre tipos, ao invés de definir funções que operam em algum valor, têm outra importância especial para a linguagem de programação Go. Métodos são o conceito fundamental por trás das interfaces.

      Interfaces

      Ao definir um método em qualquer tipo no Go, tal método será adicionado ao method set do tipo. O conjunto de métodos é a coleção de funções associadas àquele tipo como métodos, sendo usada pelo compilador do Go para determinar se algum tipo pode ser atribuído a uma variável com um tipo de interface. Um tipo de interface consiste na especificação dos métodos que o compilador utiliza para garantir que um tipo forneça as implementações para esses métodos. Qualquer tipo que possua métodos com nome, parâmetros e valores de retorno iguais àqueles encontrados na definição de uma interface são reconhecidos por implementar tal interface e podem ser atribuídos às variáveis com o mesmo tipo daquela interface. A definição da interface fmt.Stringer da bilbioteca padrão é a seguinte:

      type Stringer interface {
        String() string
      }
      

      Para que um tipo implemente a interface fmt.Stringer, ele precisa fornecer um método String() que retorna uma string. Implementar essa interface permitirá que o seu tipo seja impresso exatamente como quiser (por vezes chamado de “pretty-printed”(com estilo de formatação)) quando passar as instâncias do seu tipo para as funções definidas no pacote fmt. O exemplo a seguir define um tipo que implementa esta interface:

      package main
      
      import (
          "fmt"
          "strings"
      )
      
      type Ocean struct {
          Creatures []string
      }
      
      func (o Ocean) String() string {
          return strings.Join(o.Creatures, ", ")
      }
      
      func log(header string, s fmt.Stringer) {
          fmt.Println(header, ":", s)
      }
      
      func main() {
          o := Ocean{
              Creatures: []string{
                  "sea urchin",
                  "lobster",
                  "shark",
              },
          }
          log("ocean contains", o)
      }
      

      Quando executar o código, você verá este resultado:

      Output

      ocean contains : sea urchin, lobster, shark

      Esse exemplo define um novo tipo de struct chamado Ocean. O Ocean é conhecido por_ implementar_ a interface fmt.Stringer, uma vez que o Ocean define um método chamado String, o qual não precisa de parâmetros e retorna uma string. No main, definimos um novo Ocean e o passamos para uma função log, a qual toma uma string para imprimir primeiro, seguido de qualquer coisa que implemente o fmt.Stringer. Neste ponto, o compilador Go nos permite passar um o porque o Ocean implementa todos os métodos solicitados pelo fmt.Stringer. Dentro do log, usamos o fmt.Println, que chama o método String do Ocean quando ele encontra um fmt.Stringer como um dos seus parâmetros.

      Se o Ocean não fornecesse um método String(), o Go produziria um erro de compilação, pois o métodologsolicita um fmt.Stringer como seu argumento. O erro se parece com este:

      Output

      src/e4/main.go:24:6: cannot use o (type Ocean) as type fmt.Stringer in argument to log: Ocean does not implement fmt.Stringer (missing String method)

      O Go também irá certificar-se que o método String() fornecido corresponda exatamente ao que foi solicitado pela interface do fmt.Stringer. Se não o fizer, ele produzirá um erro que se parece com este:

      Output

      src/e4/main.go:26:6: cannot use o (type Ocean) as type fmt.Stringer in argument to log: Ocean does not implement fmt.Stringer (wrong type for String method) have String() want String() string

      Até agora, nos exemplos, definimos os métodos no receptor de valor. Ou seja, se usarmos a invocação funcional de métodos, o primeiro parâmetro – referindo-se ao tipo em que o método foi definido – será um valor desse tipo, em vez de um ponteiro. Consequentemente, quaisquer modificações que fizermos na instância fornecida para o método será descartada quando o método completar a execução, pois o valor recebido é uma cópia dos dados. Também é possível definir métodos no receptor ponteiro para um tipo.

      Ponteiros receptores

      A sintaxe para definir métodos no ponteiro receptor é quase idêntica à usada para definir os métodos no receptor de valor. A diferença é a prefixação do nome do tipo na declaração do receptor com um asterisco (*). O exemplo a seguir define um método no ponteiro receptor para um tipo:

      package main
      
      import "fmt"
      
      type Boat struct {
          Name string
      
          occupants []string
      }
      
      func (b *Boat) AddOccupant(name string) *Boat {
          b.occupants = append(b.occupants, name)
          return b
      }
      
      func (b Boat) Manifest() {
          fmt.Println("The", b.Name, "has the following occupants:")
          for _, n := range b.occupants {
              fmt.Println("t", n)
          }
      }
      
      func main() {
          b := &Boat{
              Name: "S.S. DigitalOcean",
          }
      
          b.AddOccupant("Sammy the Shark")
          b.AddOccupant("Larry the Lobster")
      
          b.Manifest()
      }
      

      Você verá o seguinte resultado quando executar este exemplo:

      Output

      The S.S. DigitalOcean has the following occupants: Sammy the Shark Larry the Lobster

      Esse exemplo definiu um tipo Boat (barco) com um Name (Nome) e os occupants (ocupantes). Queremos forçar o código em outros pacotes para adicionar apenas os ocupantes com o método AddOccupant. Assim, tornamos o campo campo occupants não exportado, deixando a primeira letra do nome do campo em letra minúscula. Também queremos garantir que chamar o AddOccupant fará a instância Boat ser modificada, motivo pelo qual definimos o AddOccupant no ponteiro receptor. Os ponteiros atuam como referência para uma uma instância específica de um tipo e não como uma cópia daquele tipo. Saber que o AddOccupant será chamado usando um ponteiro para o Boat garante que quaisquer modificações irão persistir.

      Dentro do main, definimos uma nova variável, b, a qual reterá um ponteiro para um Boat (*Boat). Invocamos o método AddOccupant duas vezes nessa instância para adicionar dois passageiros. O método Manisfest é definido no valor Boat porque, em sua definição, o receptor foi especificado como (b Boat). No main, ainda conseguimos chamar o Manifest porque o Go consegue desreferenciar automaticamente o ponteiro para obter o valor de Boat. O b.Manifest() é equivalente ao (*b). Manifest().

      O fato de um método ser definido em um ponteiro receptor ou em um receptor de valor tem implicações importantes ao se tentar atribuir valores para variáveis que sejam tipos de interface.

      Ponteiros receptores e interfaces

      Quando você atribuir um valor a uma variável com um tipo de interface, o compilador Go examinará o conjunto de métodos do tipo que está sendo atribuído para garantir que ele tenha os métodos que a interface espera. Os conjuntos de métodos para o ponteiro receptor e para o receptor de valor são diferentes pois os métodos – que recebem um ponteiro – podem modificar o seu receptor onde os que recebem um valor não podem.

      O exemplo a seguir demonstra a definição de dois métodos: uma no ponteiro receptor de um tipo e em seu receptor de valor. No entanto, apenas o ponteiro receptor poderá satisfazer a interface – também definida neste exemplo:

      package main
      
      import "fmt"
      
      type Submersible interface {
          Dive()
      }
      
      type Shark struct {
          Name string
      
          isUnderwater bool
      }
      
      func (s Shark) String() string {
          if s.isUnderwater {
              return fmt.Sprintf("%s is underwater", s.Name)
          }
          return fmt.Sprintf("%s is on the surface", s.Name)
      }
      
      func (s *Shark) Dive() {
          s.isUnderwater = true
      }
      
      func submerge(s Submersible) {
          s.Dive()
      }
      
      func main() {
          s := &Shark{
              Name: "Sammy",
          }
      
          fmt.Println(s)
      
          submerge(s)
      
          fmt.Println(s)
      }
      

      Quando executar o código, você verá este resultado:

      Output

      Sammy is on the surface Sammy is underwater

      Esse exemplo definiu uma interface chamada Submersible que espera tipos que possuam o método Dive(). Na sequência, definimos um tipo Shark com um campo Name e um método isUnderwater para monitorar o estado do Shark. Definimos um método Dive() no ponteiro receptor para o Shark, o qual modificou o isUnderwater para true. Também definimos o método String() do receptor do valor, de modo que ele pudesse imprimir claramente o estado do Shark, usando o fmt.Println, através da interface fmt.Stringer – aceita pelo fmt.Println, conforme examinamos anteriormente. Também usamos uma função submerge que recebe um parâmetro Submersible.

      Usar a interface Submersible em vez de uma *Shark permite que a função submerge dependa apenas do comportamento fornecido por um tipo. Isso torna a função submerge mais reutilizável, uma vez que você não teria que escrever novas funções submerge para um Submarine, uma Whale, ou qualquer outro habitante aquático no futuro – sobre os quais ainda nem pensamos. Contanto que eles definam um método Dive(), eles podem ser usados com a função submerge.

      Dentro do main definimos uma variável s que é um ponteiro para um Shark e que imprimiu um s imediatamente com o fmt.Println. Isso demonstra a primeira parte do resultado, Sammy is on the surface. Passamos o s para submerge e, então, chamamos o fmt.Println novamente com o s como seu argumento, a fim de verificar a segunda parte do resultado impresso, Sammy is underwater.

      Se nós mudássemos o s para ser um Shark, em vez de um *Shark, o compilador Go produziria o erro:

      Output

      cannot use s (type Shark) as type Submersible in argument to submerge: Shark does not implement Submersible (Dive method has pointer receiver)

      O lado bom é que o compilador Go nos diz que o Shark de fato tem um método Dive, o qual foi definido apenas no ponteiro receptor. Quando você ver essa mensagem em seu código, a solução é passar um ponteiro para o tipo de interface, usando o operador & antes da variável onde o tipo de valor estiver atribuído.

      Conclusão

      No final das contas, declarar os métodos no Go não é diferente de se definir as funções que recebem diferentes tipos de variáveis. Aplicam-se ao caso as mesmas regras encontradas em Trabalhando com ponteiros. O Go fornece algumas conveniências para essa definição de função extremamente comum e as coleta em conjuntos de métodos que podem ser fundamentados por tipos de interface. O uso de métodos de maneira eficaz permitirá que você trabalhe com interfaces em seu código, a fim de melhorar a capacidade de teste e deixando uma organização melhor para os futuros leitores do seu código.

      Se quiser aprender mais sobre a linguagem de programação Go de maneira geral, confira nossa série de artigos sobre Como codificar em Go.



      Source link

      Definir métodos en Go


      Introducción

      Las funciones le permiten organizar la lógica en procedimientos repetibles que pueden usar diferentes argumentos cada vez que se ejecutan. Durante la definición de las funciones, a menudo observará que varias funciones pueden funcionar sobre los mismos datos en cada ocasión. Go reconoce este patrón y le permite definir funciones especiales, llamadas métodos, cuya finalidad es operar sobre instancias de algún tipo específico, conocidas como receptores. Añadir métodos a los tipos le permite comunicar no solo lo que representan los datos, sino también cómo deberían usarse.

      Definir un método

      La sintaxis para definir un método es similar a la que se usa para definir una función. La única diferencia es la adición de un parámetro después de la palabra clave func para especificar el receptor del método. El receptor es una declaración del tipo en el que desea definir el método. El siguiente ejemplo define un método sobre un tipo estructura:

      package main
      
      import "fmt"
      
      type Creature struct {
          Name     string
          Greeting string
      }
      
      func (c Creature) Greet() {
          fmt.Printf("%s says %s", c.Name, c.Greeting)
      }
      
      func main() {
          sammy := Creature{
              Name:     "Sammy",
              Greeting: "Hello!",
          }
          Creature.Greet(sammy)
      }
      

      Si ejecuta este código, el resultado será el siguiente:

      Output

      Sammy says Hello!

      Creamos un “struct” llamado Creature con campos string para un Name y un Greeting. Este Creature tiene un único método definido, Greet. En la declaración del receptor, asignamos la instancia de Creature a la variable c para poder hacer referencia a los campos de Creature a medida que preparamos el mensaje de saludo en fmt.Printf.

      En otros lenguajes, normalmente se hace referencia al receptor de las invocaciones del método mediante una palabra clave (por ejemplo, this o self). Go considera que el receptor es una variable como cualquier otra, de modo que puede darle el nombre que usted prefiera. El estilo preferido por la comunidad para este parámetro es una versión en minúsculas del primer carácter del tipo receptor. En este ejemplo, usamos c porque el tipo receptor era Creature.

      En el cuerpo de main, creamos una instancia de Creature y especificamos los valores para sus campos Name y Greetings. Invocamos el método Greet aquí uniendo el nombre del tipo y el nombre del método con . y proporcionando la instancia de Creature como primer argumento.

      Go ofrece otra forma más conveniente de invocar métodos en instancias de un struct, como se muestra en este ejemplo:

      package main
      
      import "fmt"
      
      type Creature struct {
          Name     string
          Greeting string
      }
      
      func (c Creature) Greet() {
          fmt.Printf("%s says %s", c.Name, c.Greeting)
      }
      
      func main() {
          sammy := Creature{
              Name:     "Sammy",
              Greeting: "Hello!",
          }
          sammy.Greet()
      }
      

      Si ejecuta esto, el resultado será el mismo que en el ejemplo anterior:

      Output

      Sammy says Hello!

      Este ejemplo es idéntico al anterior, pero esta vez usamos notación de puntos para invocar el método Greet usando el Creature guardado en la variable sammy como el receptor. Esta es una notación abreviada para la invocación de función del primer ejemplo. En la biblioteca estándar y la comunidad de Go se prefiere este estilo a tal extremo que en raras ocasiones verá el estilo de invocación de función previamente mostrado.

      En el siguiente ejemplo, se muestra un motivo por el cual la notación de puntos es más frecuente:

      package main
      
      import "fmt"
      
      type Creature struct {
          Name     string
          Greeting string
      }
      
      func (c Creature) Greet() Creature {
          fmt.Printf("%s says %s!n", c.Name, c.Greeting)
          return c
      }
      
      func (c Creature) SayGoodbye(name string) {
          fmt.Println("Farewell", name, "!")
      }
      
      func main() {
          sammy := Creature{
              Name:     "Sammy",
              Greeting: "Hello!",
          }
          sammy.Greet().SayGoodbye("gophers")
      
          Creature.SayGoodbye(Creature.Greet(sammy), "gophers")
      }
      

      Si ejecuta este código, el resultado tiene este aspecto:

      Output

      Sammy says Hello!! Farewell gophers ! Sammy says Hello!! Farewell gophers !

      Modificamos los ejemplos anteriores para introducir otro método llamado SayGoodbye y también cambiamos Greet para que muestre Creature, de modo que podamos invocar métodos adicionales en esa instancia. En el cuerpo de main, invocamos los métodos Greet y SayGoodbye en la variable sammy primero usando la notación de puntos y luego usando el estilo de invocación funcional.

      Los resultados de ambos estilos son los mismos, pero el ejemplo en el que se utiliza la notación de punto es mucho más legible. La cadena de puntos también nos indica la secuencia en la cual se invocarán los métodos, mientras que el estilo funcional invierte esta secuencia. La adición de un parámetro a la invocación SayGoodbye oculta más el orden de las invocaciones del método. La claridad de la notación de puntos es el motivo por el cual es el estilo preferido para invocar métodos en Go, tanto en la biblioteca estándar como entre los paquetes externos que encontrará en el ecosistema de Go.

      La definición de métodos en tipos, en contraposición la definición de funciones que operan en algún valor, tiene otra relevancia especial en el lenguaje de programación Go. Los métodos son el concepto principal que subyace a las interfaces.

      Interfaces

      Cuando define un método en cualquier tipo en Go, ese método se añade al conjunto de métodos del tipo. El conjunto de métodos es el grupo de funciones asociadas con ese tipo como métodos y el compilador de Go los utiliza para definir si algún tipo puede asignarse a una variable con un tipo de interfaz. Un tipo de interfaz es una especificación de métodos usados por el compilador para garantizar que un tipo proporcione implementaciones para esos métodos. Se dice que cualquier tipo que tenga métodos con el mismo nombre, los mismos parámetros y los mismos valores de retorno que los que se encuentran en la definición de una interfaz_ implementan _esa interfaz y pueden asignarse a variables con ese tipo de interfaz. La siguiente es la definición de la interfaz fmt.Stringer de la biblioteca estándar:

      type Stringer interface {
        String() string
      }
      

      Para que un tipo implemente la interfaz fmt.Stringer, debe proporcionar un método String() que muestre una string. Implementar esta interfaz permitirá imprimir su tipo exactamente como lo desee (a veces esto se denomina “pretty-printed”) cuando pasa las instancias de su tipo a las funciones definidas en el paquete fmt. En el siguiente ejemplo, se define un tipo que implementa esta interfaz:

      package main
      
      import (
          "fmt"
          "strings"
      )
      
      type Ocean struct {
          Creatures []string
      }
      
      func (o Ocean) String() string {
          return strings.Join(o.Creatures, ", ")
      }
      
      func log(header string, s fmt.Stringer) {
          fmt.Println(header, ":", s)
      }
      
      func main() {
          o := Ocean{
              Creatures: []string{
                  "sea urchin",
                  "lobster",
                  "shark",
              },
          }
          log("ocean contains", o)
      }
      

      Cuando ejecute el código, verá este resultado:

      Output

      ocean contains : sea urchin, lobster, shark

      En este ejemplo se define un nuevo tipo de struct llamado Ocean. Se dice que Ocean implementa la interfaz fmt-Stringer porque Ocean define un método llamado String, que no toma ningún parámetro y muestra una string. En main, definimos un nuevo Ocean y lo pasamos a una función log, que toma una string para imprimir primero, seguida de cualquier elemento que implemente fmt.Stringer. El compilador de Go nos permite pasar o aquí porque Ocean implementa todos los métodos solicitados por fmt.Stringer. En log usamos fmt.PrintIn, que invoca el método String de Ocean cuando encuentra un fmt.Stringer como uno de sus parámetros.

      Si Ocean no proporcionara un método String(), Go produciría un error de compilación, porque el método log solicita un fmt.Stringer como su argumento. El error tiene este aspecto:

      Output

      src/e4/main.go:24:6: cannot use o (type Ocean) as type fmt.Stringer in argument to log: Ocean does not implement fmt.Stringer (missing String method)

      Go también garantizará que el método String() proporcionado coincida exactamente con el solicitado por la interfaz fmt.Stringer. Si no es así, producirá un error similar a este:

      Output

      src/e4/main.go:26:6: cannot use o (type Ocean) as type fmt.Stringer in argument to log: Ocean does not implement fmt.Stringer (wrong type for String method) have String() want String() string

      En los ejemplos analizados hasta el momento, definimos métodos en el receptor del valor. Es decir, si usamos la invocación funcional de métodos, el primer parámetro, que se refiere al tipo en el cual el método se definió, será un valor de ese tipo en vez de un puntero. Por lo tanto, cualquier modificación que realicemos a la instancia proporcionada al método se descartará cuando el método complete la ejecución, ya que el valor recibido es una copia de los datos. También es posible definir métodos sobre el receptor de punteros de un tipo.

      Receptores de punteros

      La sintaxis para definir métodos en el receptor de punteros es casi idéntica a los métodos de definición en el receptor de valores. La diferencia radica en crear un prefijo en el nombre del tipo de la declaración del receptor con un asterisco (*). En el siguiente ejemplo, se define un método sobre el receptor de punteros para un tipo:

      package main
      
      import "fmt"
      
      type Boat struct {
          Name string
      
          occupants []string
      }
      
      func (b *Boat) AddOccupant(name string) *Boat {
          b.occupants = append(b.occupants, name)
          return b
      }
      
      func (b Boat) Manifest() {
          fmt.Println("The", b.Name, "has the following occupants:")
          for _, n := range b.occupants {
              fmt.Println("t", n)
          }
      }
      
      func main() {
          b := &Boat{
              Name: "S.S. DigitalOcean",
          }
      
          b.AddOccupant("Sammy the Shark")
          b.AddOccupant("Larry the Lobster")
      
          b.Manifest()
      }
      

      Verá el siguiente resultado cuando ejecute este ejemplo:

      Output

      The S.S. DigitalOcean has the following occupants: Sammy the Shark Larry the Lobster

      En este ejemplo, se definió un tipo Boat con un Name y occupants. Queremos introducir de forma forzosa código en otros paquetes para añadir únicamente ocupantes con el método AddOccupant; por lo tanto, hicimos que el campo occupants no se exporte poniendo en minúsculas la primera letra del nombre del campo. También queremos controlar que la invocación de AddOccupant haga que la instancia de Boat se modifique, por eso definimos AddOccupant en el receptor de punteros. Los punteros actúan más como una referencia a una instancia específica de un tipo que como una copia de ese tipo. Saber que AddOccupant se invocará usando un puntero a Boat garantiza que cualquier modificación persista.

      En main, definimos una nueva variable, b, que tendrá un puntero a Boat (*Boat). Invocamos el método AddOccupant dos veces en esta instancia para añadir dos pasajeros. El método Manifest se define en el valor Boat, porque en su definición, el receptor se especifica como (b Boat). En main, aún podemos invocar Manifest porque Go puede eliminar la referencia del puntero de forma automática para obtener el valor Boat. b.Manifest() aquí es equivalente a (*b). Manifest().

      Si un método se define en un receptor de punteros o en un receptor de valor tiene implicaciones importantes cuando se intenta asignar valores a variables que son tipos de interfaz.

      Receptores de punteros e interfaces

      Cuando se asigne un valor a una variable con un tipo de interfaz, el compilador de Go examinará el conjunto de métodos del tipo que se asigna para garantizar que tenga los métodos previstos por la interfaz. Los conjuntos de métodos para el receptor de punteros y el receptor de valores son diferentes porque los métodos que reciben un puntero pueden modificar sus receptores, mientras que aquellos que reciben un valor no pueden hacerlo.

      En el siguiente ejemplo, se demuestra la definición de dos métodos: uno en el receptor de punteros de un tipo y otro en su receptor de valores. Sin embargo, solo el receptor de punteros podrá satisfacer los requisitos de la interfaz también definida en este ejemplo:

      package main
      
      import "fmt"
      
      type Submersible interface {
          Dive()
      }
      
      type Shark struct {
          Name string
      
          isUnderwater bool
      }
      
      func (s Shark) String() string {
          if s.isUnderwater {
              return fmt.Sprintf("%s is underwater", s.Name)
          }
          return fmt.Sprintf("%s is on the surface", s.Name)
      }
      
      func (s *Shark) Dive() {
          s.isUnderwater = true
      }
      
      func submerge(s Submersible) {
          s.Dive()
      }
      
      func main() {
          s := &Shark{
              Name: "Sammy",
          }
      
          fmt.Println(s)
      
          submerge(s)
      
          fmt.Println(s)
      }
      

      Cuando ejecute el código, verá este resultado:

      Output

      Sammy is on the surface Sammy is underwater

      En este ejemplo, se definió una interfaz llamada Submersible que prevé tipos que tengan un método Dive(). A continuación definimos un tipo Shark con un campo Name y un método isUnderwater para realizar un seguimiento del estado de Shark. Definimos un método Dive() en el receptor de punteros de Shark que cambió el valor de isUnderwater a true. También definimos el método String() del receptor de valores para que pudiera imprimir correctamente el estado de Shark con fmt.PrintIn usando la interfaz fmt.Stringer aceptada por fmt.PrintIn que observamos antes. También usamos una función submerge que toma un parámetro Submersible.

      Usar la interfaz Submersible en vez de *Shark permite que la función submerge dependa solo del comportamiento proporcionado por un tipo. Esto hace que la función submerge sea más reutilizable porque no tendrá que escribir nuevas funciones submerge para Submarine, Whale o cualquier otro elemento de tipo acuático que aún no se nos haya ocurrido. Siempre que definamos un método Dive(), puede usarse con la función submerge.

      En main, definimos una variable s que es un puntero de un Shark y se imprime inmediatamente s con fmt.PrintIn. Esto muestra la primera parte del resultado, Sammy is on the surface. Pasamos s a submerge y luego invocamos fmt.PrintIn de nuevo con s como argumento para ver la segunda parte del resultado impresa: Sammy is underwater.

      Si cambiamos s para que sea Shark en vez de *Shark, el compilador de Go generará un error:

      Output

      cannot use s (type Shark) as type Submersible in argument to submerge: Shark does not implement Submersible (Dive method has pointer receiver)

      El compilador de Go indica que Shark no tiene un método Dive, solo se define en el receptor de punteros. Cuando ve este mensaje en su propio código, la solución es pasar un puntero al tipo de interfaz usando el operador & antes de la variable en la que se asignó el tipo de valor.

      Conclusión

      Declarar métodos en Go no difiere de definir funciones que reciben diferentes tipos de variables. Se aplican las mismas reglas que para trabajar con punteros. Go proporciona algunas utilidades para esta definición de funciones extremadamente común y las recoge en conjuntos de métodos que pueden probarse a través de tipos de interfaz. Usar los métodos de forma efectiva le permitirá trabajar con interfaces en su código para mejorar su capacidad de prueba y proporciona una mejor organización para los lectores futuros de su código.

      Si desea obtener más información acerca del lenguaje de programación Go en general, consulte nuestra serie Cómo escribir código en Go.



      Source link

      Como usar métodos Object no JavaScript


      Introdução

      Os Objetos no JavaScript são coleções de pares chave/valor. Os valores podem consistir em propriedades e métodos e podem conter todos os outros tipos de dados do JavaScript, como strings, números e Booleans.

      Todos os objetos no JavaScript descendem do construtor pai ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)Object[. Object tem muitos métodos embutidos úteis que podemos usar e acessar para tornar o trabalho com objetos individuais em algo simplificado. Diferentemente de métodos protótipos Array como o sort() e o reverse(), que são usados na instância do array, os métodos Object são usados diretamente no construtor Object e utilizam a instância de objeto como um parâmetro. Isso é conhecido como um método estático.

      Este tutorial irá analisar importantes métodos de objetos embutidos, com cada seção abaixo tratando de um método específico e fornecendo um exemplo de uso.

      Pré-requisitos

      Para aproveitar esse tutorial ao máximo, você deve estar familiarizado em criar, modificar e trabalhar com objetos, que pode ser revisto no artigo “Entendendo objetos no JavaScript”.

      Para mais orientação com o JavaScript no geral, você pode rever nossa série Como programar em JavaScript.

      Object.create()

      O método Object.create() é usado para criar um novo objeto e conectá-lo ao protótipo de um objeto existente.

      Podemos criar uma instância de objeto job e estendê-la para um objeto mais específico.

      // Initialize an object with properties and methods
      const job = {
          position: 'cashier',
          type: 'hourly',
          isAvailable: true,
          showDetails() {
              const accepting = this.isAvailable ? 'is accepting applications' : "is not currently accepting applications";
      
              console.log(`The ${this.position} position is ${this.type} and ${accepting}.`);
          }
      };
      
      // Use Object.create to pass properties
      const barista = Object.create(job);
      
      barista.position = "barista";
      barista.showDetails();
      

      Output

      The barista position is hourly and is accepting applications.

      Agora, o objeto barista tem uma propriedade — position — mas todas as outras propriedades e métodos do job estão disponíveis pelo protótipo.Object.create()é útil para manter o código DRY minimizando a duplicação.

      Object.keys()

      O Object.keys() cria um array que contém as chaves de um objeto.

      Podemos criar um objeto e imprimir o array de chaves.

      // Initialize an object
      const employees = {
          boss: 'Michael',
          secretary: 'Pam',
          sales: 'Jim',
          accountant: 'Oscar'
      };
      
      // Get the keys of the object
      const keys = Object.keys(employees);
      
      console.log(keys);
      

      Output

      ["boss", "secretary", "sales", "accountant"]

      O Object.keys pode ser usado para iterar através das chaves e valores de um objeto.

      // Iterate through the keys
      Object.keys(employees).forEach(key => {
          let value = employees[key];
      
           console.log(`${key}: ${value}`);
      });
      

      Output

      boss: Michael secretary: Pam sales: Jim accountant: Oscar

      O Object.keys também é útil para verificar o comprimento de um objeto.

      // Get the length of the keys
      const length = Object.keys(employees).length;
      
      console.log(length);
      

      Output

      4

      Ao usar a propriedade length, conseguimos contar as 4 propriedades de employees.

      Object.values()

      O Object.values() cria uma array que contém os valores de um objeto.

      // Initialize an object
      const session = {
          id: 1,
          time: `26-July-2018`,
          device: 'mobile',
          browser: 'Chrome'
      };
      
      // Get all values of the object
      const values = Object.values(session);
      
      console.log(values);
      

      Output

      [1, "26-July-2018", "mobile", "Chrome"]

      O Object.keys() e o Object.values() permitem que você retorne dados de um objeto.

      Object.entries()

      O Object.entries() cria um array aninhado dos pares chave/valor de um objeto.

      // Initialize an object
      const operatingSystem = {
          name: 'Ubuntu',
          version: 18.04,
          license: 'Open Source'
      };
      
      // Get the object key/value pairs
      const entries = Object.entries(operatingSystem);
      
      console.log(entries);
      

      Output

      [ ["name", "Ubuntu"] ["version", 18.04] ["license", "Open Source"] ]

      Assim que tivermos os arrays de pares chave/valor, podemos usar o método forEach() para percorrer e trabalhar com os resultados.

      // Loop through the results
      entries.forEach(entry => {
          let key = entry[0];
          let value = entry[1];
      
          console.log(`${key}: ${value}`);
      });
      

      Output

      name: Ubuntu version: 18.04 license: Open Source

      O método Object.entries() irá retornar apenas as propriedades próprias da instância do objeto e não quaisquer propriedades que possam ser herdadas por meio do seu protótipo.

      Object.assign()

      O Object.assign() é usado para copiar valores de um objeto para outro.

      Podemos criar dois objetos e fundi-los com o Object.assign().

      // Initialize an object
      const name = {
          firstName: 'Philip',
          lastName: 'Fry'
      };
      
      // Initialize another object
      const details = {
          job: 'Delivery Boy',
          employer: 'Planet Express'
      };
      
      // Merge the objects
      const character = Object.assign(name, details);
      
      console.log(character);
      

      Output

      {firstName: "Philip", lastName: "Fry", job: "Delivery Boy", employer: "Planet Express"}

      Também é possível usar o operador de propagação (...) para realizar a mesma tarefa. No código abaixo, vamos modificar como declaramos o character através da fusão dos objetos name e details.

      // Initialize an object
      const name = {
          firstName: 'Philip',
          lastName: 'Fry'
      };
      
      // Initialize another object
      const details = {
          job: 'Delivery Boy',
          employer: 'Planet Express'
      };
      
      // Merge the object with the spread operator
      const character = {...name, ...details}
      
      console.log(character);
      

      Output

      {firstName: "Philip", lastName: "Fry", job: "Delivery Boy", employer: "Planet Express"}

      Essa sintaxe de propagação em literais de objeto também é conhecida como clonagem superficial (shallow-cloning).

      Object.freeze()

      O Object.freeze() impede a modificação de propriedades e valores de um objeto e impede que as propriedades sejam adicionadas ou removidas de um objeto.

      // Initialize an object
      const user = {
          username: 'AzureDiamond',
          password: 'hunter2'
      };
      
      // Freeze the object
      const newUser = Object.freeze(user);
      
      newUser.password = '*******';
      newUser.active = true;
      
      console.log(newUser);
      

      Output

      {username: "AzureDiamond", password: "hunter2"}

      No exemplo acima, tentamos substituir a senha hunter2 por *******, mas a propriedade password permaneceu a mesma. Também tentamos adicionar uma nova propriedade, active, mas ela não foi adicionada.

      O Object.isFrozen() está disponível para determinar se um objeto foi congelado ou não e retorna um Boolean.

      Object.seal()

      O Object.seal() impede que novas propriedades sejam adicionadas a um objeto, mas permite a modificação de propriedades existentes. Este método é semelhante ao Object.freeze(). Recarregue seu console antes de implementar o código abaixo para evitar um erro.

      // Initialize an object
      const user = {
          username: 'AzureDiamond',
          password: 'hunter2'
      };
      
      // Seal the object
      const newUser = Object.seal(user);
      
      newUser.password = '*******';
      newUser.active = true;
      
      console.log(newUser);
      

      Output

      {username: "AzureDiamond", password: "*******"}

      A nova propriedade active não foi adicionada ao objeto selado, mas a propriedade password foi alterada com sucesso.

      Object.getPrototypeOf()

      O Object.getPrototypeOf() é usado para obter o [[Prototype]] interno oculto de um objeto, também acessível através da propriedade __proto__.

      Neste exemplo, podemos criar um array, que tenha acesso ao protótipo Array.

      const employees = ['Ron', 'April', 'Andy', 'Leslie'];
      
      Object.getPrototypeOf(employees);
      

      Output

      [constructor: ƒ, concat: ƒ, find: ƒ, findIndex: ƒ, pop: ƒ, …]

      Podemos ver no resultado que o protótipo do array employees tem acesso aos métodos de protótipo Array pop, find, entre outros. Podemos confirmar isso testando o protótipo employees contra o Array.prototype.

      Object.getPrototypeOf(employees) === Array.prototype;
      

      Output

      true

      Este método pode ser útil para obter mais informações sobre um objeto ou garantir que ele tenha acesso ao protótipo de outro objeto.

      Também há um método Object.setPrototypeOf() relacionado que adicionará um protótipo a outro objeto. Ao invés disso, é recomendável que você utilize o Object.create() uma vez que ele é mais rápido e tem maior desempenho.

      Conclusão

      Os objetos têm muitos métodos úteis que nos ajudam a modificar, proteger e iterar através deles. Neste tutorial, vimos novamente como criar e atribuir novos objetos, iterar através das chaves e/ou valores de um objeto, e congelar ou selar um objeto.

      Se você precisar revisar os objetos do JavaScript, leia “Entendendo objetos no JavaScript”. Se quiser se familiarizar com a cadeia de protótipos, você pode olhar “Entendendo protótipos e herança no JavaScript”.



      Source link