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 em: Go 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{ ... }” 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.
Então o que temos até o momento é 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 demostra o comportamento do que seria "Dunk typing" apresentando um polimorfismo utilizando Go e composição utilizando structs e alguns tipos diferentes.
package main
import (
"fmt"
)
type Familia interface {
Dados() string
}
type Pai struct {
Nome string
Idade int
Cpf string `json:"cpf"`
}
func (p Pai) Dados() string {
return fmt.Sprintf("Nome: %s, Idade: %d", p.Nome, p.Idade)
}
type Filho struct {
Pai Pai
Idade int
Nome string
Email string
}
func (f Filho) Dados() string {
return fmt.Sprintf("\nNome: %s, Idade: %d, Email: %s", f.Nome, f.Idade, f.Email)
}
type Filhos []Filho
func (f Filhos) Dados() string {
concat := ""
for _, v := range f {
concat += fmt.Sprintf("\nNome: %s, Idade: %d, Email: %s", v.Nome, v.Idade, v.Email)
}
return concat
}
func showDados(membro Familia) {
fmt.Println(membro.Dados())
}
func showDados2(f []Familia) {
for _, membro := range f {
fmt.Println(membro.Dados())
}
}
func main() {
//// Pai
pai := new(Pai)
pai.Nome = "Jefferson"
pai.Idade = 38
pai.Cpf = "00.xxx.xxx-xx"
var filho Filho
var filhos Filhos
//// Filhos
filho.Pai.Nome = "Pai Nome Here"
filho.Pai.Idade = 38
filho.Pai.Cpf = "01.xxx.xxx-xx"
filho.Nome = "Arthur"
filho.Email = "arthur@gmail.com"
filho.Idade = 4
filhos = append(filhos, filho)
filho.Nome = "Francisco"
filho.Email = "francisco@gmail.com"
filho.Idade = 7
filhos = append(filhos, filho)
//// Filha
filha := new(Filho)
filha.Nome = "Joyce"
filha.Idade = 22
filha.Email = "joycexxx@gmail.com"
// Show Dados
showDados(pai)
showDados(filha)
showDados(filhos)
idadeFilhos := []Familia{pai, filha, filhos}
showDados2(idadeFilhos)
}
Confira o exemplo completinho aqui: executar o exemplo
A interface é uma coleção de métodos, além de ser um tipo personalizado.
type Familia interface {
// Assinatura Função
}
type Familia interface {
Dados() string
}
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.
type Pai struct {
Nome string
Idade int
Cpf string `json:"cpf"`
}
func (p Pai) Dados() string {
return fmt.Sprintf("Nome: %s, Idade: %d", p.Nome, p.Idade)
}
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.
type Filho struct {
Pai Pai
Idade int
Nome string
Email string
}
func (f Filho) Dados() string {
return fmt.Sprintf("\nNome: %s, Idade: %d, Email: %s", f.Nome, f.Idade, f.Email)
}
type Filhos []Filho
func (f Filhos) Dados() string {
concat := ""
for _, v := range f {
concat += fmt.Sprintf("\nNome: %s, Idade: %d, Email: %s", v.Nome, v.Idade, v.Email)
}
return concat
}
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.
func showDados(membro Familia) {
fmt.Println(membro.Dados())
}
Olha o exemplo abaixo, temos vários tipos de objetos sendo passado para a função showDados(…).
func main() {
//// Pai
pai := new(Pai)
pai.Nome = "Jefferson"
pai.Idade = 38
pai.Cpf = "00.xxx.xxx-xx"
var filho Filho
var filhos Filhos
//// Filhos
filho.Pai.Nome = "Pai Nome Here"
filho.Pai.Idade = 38
filho.Pai.Cpf = "01.xxx.xxx-xx"
filho.Nome = "Arthur"
filho.Email = "arthur@gmail.com"
filho.Idade = 4
filhos = append(filhos, filho)
filho.Nome = "Francisco"
filho.Email = "francisco@gmail.com"
filho.Idade = 7
filhos = append(filhos, filho)
//// Filha
filha := new(Filho)
filha.Nome = "Joyce"
filha.Idade = 22
filha.Email = "joycexxx@gmail.com"
// Show Dados
showDados(pai)
showDados(filha)
showDados(filhos)
}
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:
idadeFilhos := []Familia{pai, filha, filhos}
showDados2(idadeFilhos)
A função showDados2(f []Familia) recebe um vetor da “interface Familia”.
func showDados2(f []Familia) {
for _, membro := range f {
fmt.Println(membro.Dados())
}
}
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
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.
Top comments (1)
ótimo artigo e realmente é uma discussao que causa polemica.