DEV Community

Cover image for Go é orientado a objetos ?
jefferson otoni lima
jefferson otoni lima

Posted on

Go é orientado a objetos ?

#go

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)

}

Enter fullscreen mode Exit fullscreen mode

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
}

Enter fullscreen mode Exit fullscreen mode

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)
}

Enter fullscreen mode Exit fullscreen mode

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
}

Enter fullscreen mode Exit fullscreen mode

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())
}

Enter fullscreen mode Exit fullscreen mode

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)
}

Enter fullscreen mode Exit fullscreen mode

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)

Enter fullscreen mode Exit fullscreen mode

A função showDados2(f []Familia) recebe um vetor da “interface Familia”.


func showDados2(f []Familia) {
    for _, membro := range f {
        fmt.Println(membro.Dados())
    }
}

Enter fullscreen mode Exit fullscreen mode

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.

  1. builtin.Error
  2. fmt.Stringer
  3. io.Reader
  4. io.Writer
  5. io.ReadWriteCloser
  6. http.ResponseWriter
  7. http.Handler

Aqui uma lista completa de interfaces em Go

Lista completa

Caso queiram acessar o código fonte do exemplo apresentado e mais exemplos basta visitar este link.

github.com/jeffotoni/goexample

Top comments (1)

Collapse
 
ironbats profile image
ironbats

ótimo artigo e realmente é uma discussao que causa polemica.