
Go é orientado a objetos ?
Fala comigo galerinha, a semana começou agitada 😂, este post é sobre Orientação Objeto na linguagem de programação Go. É um assunto que tornou-se recorrente em listas de discussões, MeetUp, grupos etc. Quando o assunto é Programação Orientada a Objetos em Go causa uma polêmica.
Sabemos que a Linguagem Go é um Like C e também sabemos que a linguagem Go teve influências de diversas outras linguagens de programação e paradigmas diferentes, dentre elas: Alef, APL, BCPL, C, CSP, Limbo, Modula, Newsqueak, Oberon, occam,Pascal, Smalltalk e Cristal. Como podem perceber temos uma miscelânea de paradigmas porém o paradigma Imperativo e Concorrente são os que mais predominam em Go.
Go é uma linguagem orientada a objetos a resposta para esta pergunta é SIM e NÃO 😱. No site oficial Go em faq encontraremos alguns pontos sobre isto caso queira da uma conferida só clicar aqui Go an object-oriented language
Em Go conseguimos desenvolver um estilo de programação orientada a objetos permitindo construir tipos e métodos porém não há hierarquia de tipos, é bem diferente da implementação que conhecemos em outras linguagens de programação porque não temos um arsenal de recursos como Linguagens desenvolvidas para o paradigma orientadas a objetos.
O que temos em Go é uma abstração para flexibilizar e ajudar quando precisarmos utilizar diferentes tipos de structs sem nos preocupar qual struct estará sendo utilizada e o mais legal deixa encapsulado seu método de forma segura.
Bem para que isto seja possível em Go temos um coadjuvante que se chama “interface” 💪🏼 ou seja type MyInterface interface{ // assinatura } e sua abordagem como dissemos é diferente das implementações que conhecemos em outras langs, interface é a única forma de permitir o despacho de métodos dinâmicos em Go e podemos conter valores de qualquer tipo. (Cada tipo implementa pelo menos zero métodos).
Interfaces vazias são usadas por código que lida com valores de tipo desconhecido que é definido da seguinte forma interface{} olha um exemplo abaixo.
No exemplo acima mostramos como a interface recebe qualquer tipo em Go, usando interface podemos passar qualquer tipo para ela isto nos permite a criar funções que o tipo é desconhecido vamos ver um exemplo:
Criamos uma função que recebe qualquer tipo, e demonstrei no exemplo acima como acessar seus valores quando temos um tipo interface. Para acessar seus respectivos valores temos que fazer um Assertion.
Um type assertion permite o acesso a valores de um tipo através de uma valor concreto t := v.(T)
. Essa expressão declara que o valor de v
é um tipo concreto de T
e define v
como T
e atribui a t
.
É recomendado sempre testarmos antes utilizando switch.
Isto é um erro comum quando usamos type assertion devemos testar se v
é do tipo T
ates de realizar assertion, isso gera um panic
na nosso programa. Quando fazemos o test ele evita esse tipo de erro e ainda permite verificar se a conversão funcionou.t, ok := v.(T)
O type switch é igual a uma expressão switch normal, porém o switch irá ser aplicado sobre o tipo e não o valor. O tipo do valor é comparado a vários tipos. A declaração realiza os teste e executa o bloco de código quando o tipo do valor contiver o tipo testado.
Agora que já vimos um exemplo da utilização de interface vazia Iremos abordar interface como uma coleção de métodos.
Alguns pontos importantes sobre interface
- Um tipo de interface é definido como um conjunto de assinaturas de método.
- Um valor do tipo de interface pode conter qualquer valor que implemente esses métodos.
- As interfaces são implementadas implicitamente.
- Não há declaração explícita.
- Nos bastidores, os valores da interface podem ser considerados como uma tupla de um valor e um tipo concreto: (value, type)
- Um valor de interface contém um valor de um tipo concreto subjacente específico.
- Chamar um método em um valor de interface executa o método de mesmo nome em seu tipo subjacente.
- Se o valor concreto dentro da própria interface for nulo, o método será chamado com um receptor nulo.
- um valor de interface que contém um valor concreto nulo é ele próprio não nulo.
- Um valor de interface nulo não contém valor nem tipo concreto.
- Chamar um método em uma interface nula é um erro em tempo de execução porque não há nenhum tipo dentro da tupla da interface para indicar qual método concreto chamar.
type myInface interface { //assinatura }
Então o que temos até o momento em Go é uma forma de fazer polimorfismo utilizando interface, encapsulamento de métodos e disponibiliza-los de forma dinâmica tudo isto é muito semelhante ao “Duck typing”.
O que é Duck typing
O Go suporta “Duck typing”, é um estilo de tipagem em que os métodos e propriedades de um objeto determinam a semântica válida, em vez de sua herança de uma classe particular ou implementação de uma interface explicita. O nome do conceito refere-se ao teste do pato, atribuído à James Whitcomb Riley. Isso faz com que o Go se pareça com uma linguagem dinâmica.
Go usa “Tipagem Estrutural“ em métodos para determinar a compatibilidade de um tipo com uma interface. Não há hierarquias de tipos e a “Tipagem Estrutural” é uma alternativa interessante à herança clássica em linguagens com tipagem estática. Ele permite que você escreva algoritmos genéricos sem obscurecer a definição de um tipo em um mar de interfaces. Talvez mais importante, ajuda as linguagens com tipagem estática a capturar a sensação e a produtividade das linguagens dinamicamente tipificadas.
Duck typing ocorre em tempo de execução e “Tipagem Estrutural” que ocorre em tempo de compilação. Go não da suporte a orientação a objetos como é implementado nas linguagens como C#, Java ou mesmo C ++, não possui herança e honestamente fico muito feliz com isto, mas oferece alguns recursos como composição e interfaces como descrevemos acima.
O exemplo abaixo demonstra o comportamento do que seria “Dunk typing” e polimorfismo utilizando Go.
Caso queira executar o exemplo basta clicar no link: play.golang.org/familia-interface.go
A interface é uma coleção de métodos, além de ser um tipo personalizado.
Dizemos que algo satisfaz esta interface (ou implementa esta interface ) se tiver um método com a assinatura exata Dados() string.
Por exemplo, a struct Pai satisfaz a interface porque tem um Dados() string definido como método.
Não é realmente importante o que esse “Pai” é ou faz. A única coisa que importa é que tem um método chamado Dados() que retorna uma string.
Ou, como Filhos, o tipo a seguir também satisfaz a “interface Familia” novamente porque tem um método com a assinatura exata Dados() string.
O importante aqui é que temos três tipos diferentes Pai, Filho e Filhos, que fazem coisas diferentes. Mas o que eles têm em comum é que ambos satisfazem a interface Familia.
Podemos pensar sobre isto de outra maneira. Se você sabe que um objeto satisfaz a interface Familia, pode confiar que ele tem um método com a assinatura exata Dados() string que pode ser chamada.
Agora vamos conferir nossa função showDados(…) que será responsável por acessar todos nossos métodos diferentes atendendo a mesma interface, agora você poderá usar objetos de qualquer tipo desde que satisfaça a interface.
Olha o exemplo abaixo, temos vários tipos de objetos sendo passado para a função showDados(…).
As possibilidades são diversas, podemos definir uma interface recebendo outra interface, podemos criar outras funções que satisfaça a interface como o exemplo abaixo:
A função showDados2(f []Familia) recebe um vetor da interface Familia.
Simples não é ? Com esta definição temos diversas possibilidades que poderá utilizar interface em seus projetos.
Clean Architecture é um bom exemplo desta utilização também, toda sua arquitetura se baseia-se em interfaces caso queiram da uma conferida alguns links legais “Clean Architecture using Golang” e Clean Architecture, 2 years later
Existem alguns motivos pelos quais você poderia usar interface em Go, vou listar os três mais comuns:
. Para ajudar a reduzir a duplicação ou código padrão.
. Para tornar mais fácil usar mocks em vez de objetos reais em testes de unidade.
. Como uma ferramenta de arquitetura, para ajudar a impor o desacoplamento entre as partes de sua base de código.
Algumas interfaces utilizada em Go em sua strand libray default
Uma pequena lista de algumas das interfaces mais comuns e úteis na biblioteca padrão.
- builtin.Error
- fmt.Stringer
- io.Reader
- io.Writer
- io.ReadWriteCloser
- http.ResponseWriter
- http.Handler
- Sort
Vamos da uma olhada no pkg sort
Um tipo, normalmente uma coleção, que satisfaz a classificação. A interface pode ser classificada pelas funções neste pacote. Os métodos requerem que os elementos da coleção sejam enumerados por um índice inteiro.
O que vemos logo abaixo são as assinaturas que a interface Sort possui
Quando formos usar a interface iremos ter que criar os três métodos que satisfaça exatamente a Interface, e o que muda sempre são os tipos e structs que poderão implementar os métodos que satisfaça a interface.
A desvantagem aqui é que não necessariamente precisaria dos três métodos para Ordenar minha struct precisaria somente do Less(…), porém somos obrigados a satisfazer a interface exatamente como ele está assinada ou seja precisaremos sempre implementar Less(…), Len(…), Swap(…).
Como boa prática sempre é interessante termos interfaces mais enxutas possíveis.
Uma curiosidade aqui neste pkg é a possibilidade de utilizar uma interface incorporada e que o Reverse use os métodos de implementação de outra interface 😱.
Legal não é ? Ou seja o Less retorna o oposto do método Less da implementação incorporada.
E para fechar temos diversos tipos diferentes criando e implementando seus próprios métodos Less(…), Len() e Swap(..)
Aqui uma lista completa de interfaces em Go
Caso queiram acessar o código fonte do exemplo apresentado e mais exemplos basta visitar este link.
github.com/jeffotoni/goexample
Conclusão
Espero que tenha gostado do resumo e uma breve introdução do que podemos fazer em Go quando o assunto é Orientação a Objeto. Qualquer observação, dica, melhoria etc.. no conteúdo por favor só enviar para melhora-lo ainda mais.
Percebemos que na prática a utilização de interfaces é bem mais simples do que a explicação teórica. E com a chegada de generics os problemas que resolvemos utilizando interface serão todos repensados após o lançamento de generics.