A linguagem Go, também conhecida como GoLang, é uma linguagem open source que foi criada pelo Google em 2007, e desde então é utilizada para a construção de produtos e serviços de grande escala. Atualmente a linguagem é utilizada por diversas empresas, como Uber, Twitch, Medium e Mercado livre.
Go é uma linguagem simples e produtiva de se utilizar, com foco no desenvolvimento de aplicações que necessitam de alta performance. Embora tenha sido criada para lidar com sistemas de redes e infraestrutura, Go também é bastante utilizada no mercado para:
Desenvolvimento de aplicações server-side e hospedadas em ambientes cloud; Construção de scripts e ferramentas de automações utilizadas por times DevOps; Construção de ferramentas de linha de comando; Soluções de inteligência artifical e data science.
Fonte: Formação Go
Curso 02 - Go: Orientação a Objetos
Repositório Iniciando com o Go
- Golang Orientado a Objetos
- Struct
- New e Ponteiros
- Funções e Ponteiros
- Múltiplos Retornos
- Go Mod
- Módulos e Pacotes
- Composição e Pacotes em Go
- Interface
Embora Go não suporte herança tradicional como outras linguagens OOP (por exemplo, Java ou C++), ele permite a implementação de muitos conceitos de OOP através de suas próprias construções, como estruturas e interfaces.
-
Estruturas (
struct
): Go utiliza estruturas para definir tipos de dados compostos. Estruturas são semelhantes às classes em outras linguagens OOP, permitindo agrupar campos relacionados e definir métodos associados. No entanto, não há suporte nativo para métodos incorporados diretamente nas estruturas; em vez disso, os métodos são definidos com um receptor de estrutura. -
Métodos: Funções associadas a tipos específicos (estruturas) usando um receptor. Os métodos permitem que você defina comportamentos que podem operar sobre as instâncias das suas estruturas.
-
Encapsulamento: Em Go, o encapsulamento é alcançado através da convenção de nomenclatura: identificadores que começam com letras maiúsculas são exportados e acessíveis fora do pacote, enquanto aqueles que começam com letras minúsculas são privados ao pacote. Assim, você pode controlar a visibilidade de campos e métodos.
-
Composição: Em vez de herança, Go utiliza composição para reutilizar código. Isso é feito incluindo um tipo em outro tipo (estrutura), permitindo que você aproveite o comportamento de um tipo dentro de outro. Isso oferece flexibilidade e permite a criação de tipos mais complexos sem a necessidade de uma hierarquia de herança.
-
Interfaces: Interfaces são um conceito fundamental em Go para definir comportamentos que tipos devem implementar. Elas permitem a abstração e o polimorfismo, permitindo que diferentes tipos implementem o mesmo conjunto de métodos. Isso permite a criação de código que pode trabalhar com qualquer tipo que implemente uma interface específica, promovendo a reutilização e a flexibilidade.
Principais Conceitos de OOP em Go:
- Estruturas e Métodos: Usados para definir tipos de dados e comportamentos.
- Encapsulamento: Através de nomenclatura e visibilidade de campos e métodos.
- Composição: Reutilização de código sem herança tradicional.
- Interfaces: Definem comportamentos e permitem polimorfismo.
Apesar de não ter suporte para herança tradicional, Go fornece ferramentas poderosas para organizar e estruturar código de maneira eficiente e modular, utilizando conceitos de OOP adaptados à sua filosofia de design.
Em Go, uma struct é uma coleção de campos que podem conter diferentes tipos de dados. Structs são usadas para agrupar dados relacionados, facilitando a organização e manipulação dessas informações. Ao definir uma struct, você cria um novo tipo que pode ser utilizado para instanciar variáveis contendo múltiplos valores agrupados.
Exemplo de Definição e Uso de uma Struct:
type ContaCorrente struct {
titular string
numeroAgencia int
numeroConta int
saldo float64
}
func main() {
fmt.Println(ContaCorrente{})
}
No exemplo acima, a struct ContaCorrente
é definida com quatro campos: titular
, numeroAgencia
, numeroConta
e saldo
. Esses campos representam as características de uma conta corrente.
Quando uma struct é instanciada sem valores iniciais, como em ContaCorrente{}
, seus campos assumem os Zero Values de seus respectivos tipos.
Zero Value:
O conceito de "Zero Value" em Go refere-se ao valor padrão que é atribuído automaticamente a cada campo de uma struct quando ela é inicializada sem valores. O Zero Value varia de acordo com o tipo de dado:
- Para strings:
""
(string vazia) - Para inteiros:
0
- Para floats:
0.0
- Para booleanos:
false
- Para structs:
{}
- Para pointers, slices, maps, channels, funções e interfaces:
nil
Exemplo de Saída com Zero Value:
PS E:\alura\github\curso-go-poo\src> go run .\main.go
{ 0 0 0}
Neste exemplo, a struct ContaCorrente
foi inicializada sem valores, resultando na saída { 0 0 0}
. Isso ocorre porque titular
é uma string que assume o valor vazio ""
, enquanto numeroAgencia
, numeroConta
e saldo
são numéricos e assumem o valor 0
.
Esse comportamento é útil porque permite a criação de structs sem precisar inicializar todos os campos explicitamente, utilizando os Zero Values como padrões.
Alimentando uma Struct
Em Go, você pode inicializar e alimentar uma struct com valores específicos de duas maneiras: usando a sintaxe de inicialização nomeada ou a sintaxe posicional. Ambas permitem definir valores para os campos da struct, mas têm algumas diferenças na forma como os dados são fornecidos.
Exemplo de Inicialização Nomeada:
contaDoDenisson := ContaCorrente{
titular: "Denisson Freitas",
numeroAgencia: 1234,
numeroConta: 54321,
saldo: 125.50,
}
Neste exemplo, os valores são atribuídos a campos específicos da struct ContaCorrente
usando a sintaxe nomeada. Essa abordagem é útil quando você deseja garantir que os valores sejam atribuídos corretamente aos campos apropriados e melhora a legibilidade do código.
Exemplo de Inicialização Posicional:
contaDoBinho := ContaCorrente{
"Binho Filho", // titular
9876, // numeroAgencia
12345, // numeroConta
1000, // saldo
}
No exemplo acima, os valores são atribuídos aos campos da struct ContaCorrente
em uma ordem posicional, correspondente à ordem de declaração dos campos na struct. Essa abordagem é mais compacta, mas menos explícita, e pode ser mais suscetível a erros se a ordem dos campos não for bem compreendida.
Ambos os métodos são válidos e podem ser usados conforme a necessidade. A escolha entre eles geralmente depende da clareza desejada e da complexidade da struct.
Em Go, além da inicialização direta de structs, é possível manipular e condicionar valores a elas utilizando ponteiros. Utilizar ponteiros pode ser útil quando você precisa modificar a struct em diferentes partes do código ou quando deseja evitar a cópia de grandes estruturas de dados.
Ponteiros e a Função new
:
A função new
em Go é usada para alocar memória para uma nova variável do tipo especificado e retorna um ponteiro para essa variável. Esse ponteiro pode ser utilizado para acessar e modificar a struct diretamente.
Exemplo de Uso de Ponteiros com new
:
// Utilizando ponteiros
var contaDaCris *ContaCorrente
contaDaCris = new(ContaCorrente)
contaDaCris.titular = "Cris"
contaDaCris.numeroAgencia = 2654
contaDaCris.numeroConta = 12456
fmt.Println(*contaDaCris)
No exemplo acima:
-
Declaração do Ponteiro:
var contaDaCris *ContaCorrente
Aqui,
contaDaCris
é declarado como um ponteiro para umaContaCorrente
. -
Alocação de Memória com
new
:contaDaCris = new(ContaCorrente)
A função
new(ContaCorrente)
aloca memória para uma novaContaCorrente
e retorna um ponteiro para essa memória. O ponteiro é então atribuído à variávelcontaDaCris
. -
Atribuição de Valores:
contaDaCris.titular = "Cris" contaDaCris.numeroAgencia = 2654 contaDaCris.numeroConta = 12456
Os campos da struct são acessados e modificados através do ponteiro. Quando você usa o ponteiro
contaDaCris
, você está manipulando diretamente a instância da struct alocada na memória. -
Impressão da Struct:
fmt.Println(*contaDaCris)
O operador
*
é usado para desreferenciar o ponteiro e acessar o valor da struct. Sem esse operador, você estaria imprimindo o endereço de memória.
Usar ponteiros pode ser benéfico para evitar a cópia de structs grandes, reduzir o uso de memória e permitir que você modifique a struct original sem criar cópias adicionais. No entanto, é importante gerenciar ponteiros com cuidado para evitar problemas como referências nulas ou vazamentos de memória.
Em Go, você pode definir métodos para structs que utilizam ponteiros como receivers. Isso é especialmente útil quando você precisa modificar os campos da struct dentro de uma função, pois o uso de ponteiros permite que a função altere diretamente os dados originais em vez de trabalhar com uma cópia.
Exemplo de Uso de Funções com Ponteiros:
func main() {
var contaDaCris *ContaCorrente
contaDaCris = new(ContaCorrente)
contaDaCris.titular = "Cris"
contaDaCris.numeroAgencia = 2654
contaDaCris.numeroConta = 12456
contaDaCris.saldo = 1000
fmt.Println(*contaDaCris)
// Realizando Saque
fmt.Println(contaDaCris.sacar(500))
fmt.Println(*contaDaCris)
}
func (c *ContaCorrente) sacar(valorDoSaque float64) string {
podeSacar := valorDoSaque <= c.saldo && valorDoSaque > 0
if podeSacar {
c.saldo -= valorDoSaque
return "Saque realizado com sucesso!"
} else {
return "Saldo insuficiente."
}
}
Neste exemplo:
-
Declaração da Struct e Inicialização: A struct
ContaCorrente
é instanciada usando a funçãonew
, e os campos da struct são preenchidos com valores. -
Uso de Ponteiros como Receivers: A função
sacar
é definida com um receiver que é um ponteiro paraContaCorrente
:func (c *ContaCorrente) sacar(valorDoSaque float64) string
Isso significa que quando
sacar
é chamado, ele pode modificar diretamente os campos da struct original, como o camposaldo
. -
Modificação dos Campos: Dentro da função
sacar
, o valor do saque é subtraído diretamente do saldo da conta, utilizando o ponteiroc
. Comoc
é um ponteiro, a modificação afeta a struct original em memória, e não uma cópia. -
Chamada da Função: No
main
, o métodosacar
é chamado usandocontaDaCris.sacar(500)
. Após a execução da função, o saldo da conta é reduzido, refletindo a operação de saque.
Usar ponteiros como receivers em funções é uma prática comum em Go quando você precisa alterar o estado de uma struct. Isso permite que as alterações feitas dentro da função sejam persistentes e reflitam diretamente na instância original da struct.
Em Go, as funções e métodos podem retornar múltiplos valores. Isso é útil em situações onde você deseja retornar tanto um resultado primário quanto informações adicionais, como um status ou um erro. O exemplo a seguir demonstra como utilizar múltiplos retornos em um método que realiza depósitos em uma conta corrente.
Exemplo de Método com Múltiplos Retornos:
func (c *ContaCorrente) Depositar(valorDoDeposito float64) (string, float64) {
podeDepositar := valorDoDeposito > 0
if podeDepositar {
c.saldo += valorDoDeposito
return "Depósito realizado com sucesso!", c.saldo
} else {
return "Não foi possível realizar o depósito. Entre com um valor válido.", c.saldo
}
}
Neste exemplo:
-
Definição do Método
Depositar
: O métodoDepositar
recebe um valor de depósito como argumento e retorna duas informações:- Uma mensagem de status (
string
) - O saldo atual da conta (
float64
)
- Uma mensagem de status (
-
Verificação e Atualização: O método verifica se o valor do depósito é positivo. Se for, o saldo da conta é atualizado e uma mensagem de sucesso é retornada junto com o novo saldo. Caso contrário, uma mensagem de erro é retornada junto com o saldo atual, que não é alterado.
Exemplo de Uso de Múltiplos Retornos:
fmt.Println(contaDaCris.Depositar(1000)) // Saída: "Depósito realizado com sucesso!", 2000
fmt.Println(*contaDaCris) // Saída: {Cris 2654 12456 2000}
status, saldo := contaDaCris.Depositar(200)
fmt.Println(status) // Saída: "Depósito realizado com sucesso!"
fmt.Println(saldo) // Saída: 2200
Neste trecho de código:
-
Chamada com
fmt.Println
: A primeira chamada aDepositar
é usada diretamente dentro defmt.Println
, que imprime os dois valores retornados. -
Atribuição a Variáveis: Na segunda chamada, os valores retornados por
Depositar
são capturados em variáveis separadas,status
esaldo
. Isso permite um maior controle sobre como cada valor é manipulado e exibido posteriormente.
O uso de múltiplos retornos em Go permite que funções e métodos sejam mais expressivos e forneçam informações adicionais sem a necessidade de estruturas mais complexas como tuplas ou objetos. Isso é especialmente útil para retornar um resultado primário e um status ou erro associado.
O go mod
é uma ferramenta usada no Go para gerenciar dependências de projetos, introduzida a partir da versão 1.11 do Go. Com o conceito de módulos, o go mod
facilita o gerenciamento de bibliotecas e pacotes externos, eliminando a necessidade de configurar o GOPATH e proporcionando uma abordagem mais moderna e eficiente para o controle de dependências.
O que é go.mod
?
O arquivo go.mod
é o arquivo de configuração principal para um módulo Go. Ele contém informações sobre o módulo, incluindo:
- O nome do módulo, que geralmente corresponde ao caminho do repositório onde o código está armazenado.
- As versões das dependências utilizadas pelo projeto.
- Outras informações de configuração do módulo, como os requisitos de versão do Go.
Como criar o go.mod
?
Para criar um arquivo go.mod
, você precisa inicializar um módulo no seu projeto. Isso pode ser feito utilizando o comando go mod init
. Siga os passos abaixo:
- Abra um terminal e navegue até o diretório do seu projeto.
- Execute o comando
go mod init
seguido do caminho do módulo (geralmente o caminho do repositório).go mod init exemplo.com/meuprojeto
- O Go criará um arquivo
go.mod
no diretório do projeto com a configuração inicial do módulo.
Benefícios do go.mod
:
- Gerenciamento Simplificado de Dependências: O
go mod
facilita o gerenciamento das versões das dependências, permitindo que você especifique versões exatas e bloqueie versões específicas para garantir a consistência do projeto. - Isolamento do GOPATH: Com o uso de módulos, não é mais necessário configurar o GOPATH, tornando o gerenciamento de projetos mais simples e menos propenso a erros.
- Reprodutibilidade e Consistência: O arquivo
go.mod
e o arquivogo.sum
(que registra as somas de verificação das dependências) ajudam a garantir que todos os desenvolvedores e ambientes de build utilizem as mesmas versões das dependências, promovendo a consistência do projeto. - Facilidade na Importação de Pacotes: Com o
go.mod
, você pode importar pacotes diretamente sem precisar ajustar o GOPATH, facilitando a inclusão de bibliotecas externas no seu projeto.
Exemplo de go.mod
:
module exemplo.com/meuprojeto
go 1.18
require (
github.com/some/dependency v1.2.3
github.com/another/dependency v4.5.6
)
No exemplo acima, o arquivo go.mod
define o módulo como exemplo.com/meuprojeto
, especifica a versão do Go e lista as dependências do projeto com suas versões correspondentes.
Utilizar go mod
é uma prática recomendada para projetos Go modernos, pois oferece uma abordagem mais robusta e flexível para gerenciamento de dependências.
Em Go, um package (ou pacote) é uma maneira de organizar o código em módulos reutilizáveis. Um pacote é um conjunto de arquivos Go que estão localizados no mesmo diretório e têm o mesmo nome de pacote. Pacotes permitem que você agrupe funções, tipos e variáveis relacionadas, facilitando a modularização e a reutilização do código.
Como funciona?
Cada arquivo Go em um diretório faz parte de um pacote. O nome do pacote é declarado no início de cada arquivo com a palavra-chave package
. Quando você importa um pacote em outro arquivo, pode acessar as funções, tipos e variáveis exportados pelo pacote.
Regras de Visibilidade:
- Público: Identificadores (como funções, métodos, variáveis e tipos) que começam com uma letra maiúscula são exportados e, portanto, públicos. Isso significa que eles podem ser acessados a partir de outros pacotes.
- Privado: Identificadores que começam com uma letra minúscula não são exportados e, portanto, privados. Esses identificadores só podem ser acessados dentro do próprio pacote.
Como utilizar?
Para utilizar um pacote, você precisa importá-lo usando a palavra-chave import
. O caminho do pacote especifica onde ele está localizado. Você pode então acessar os identificadores exportados do pacote.
Exemplo:
Arquivo contas.go
no pacote contas
:
package contas
type ContaCorrente struct {
Titular string
NumeroAgencia int
NumeroConta int
Saldo float64
}
func (c *ContaCorrente) Sacar(valorDoSaque float64) string {
podeSacar := valorDoSaque <= c.Saldo && valorDoSaque > 0
if podeSacar {
c.Saldo -= valorDoSaque
return "Saque realizado com sucesso!"
} else {
return "Saldo insuficiente."
}
}
func (c *ContaCorrente) Depositar(valorDoDeposito float64) (string, float64) {
podeDepositar := valorDoDeposito > 0
if podeDepositar {
c.Saldo += valorDoDeposito
return "Depósito realizado com sucesso!", c.Saldo
} else {
return "Não foi possível realizar o depósito. Entre com um valor válido.", c.Saldo
}
}
func (c *ContaCorrente) Transferir(valorDaTransferencia float64, contaDestino *ContaCorrente) string {
podeTransferir := valorDaTransferencia > 0 && c.Saldo > valorDaTransferencia
if podeTransferir {
c.Saldo -= valorDaTransferencia
contaDestino.Depositar(valorDaTransferencia)
return "Transferência realizada com sucesso!"
} else {
return "Ocorreu um erro na transferência. Por favor, verifique os valores inseridos."
}
}
Arquivo main.go
no pacote main
:
package main
import (
"curso-go-poo/pkg/contas"
"fmt"
)
func main() {
contaDaCris := new(contas.ContaCorrente)
contaDaCris.Titular = "Cris Souza"
contaDaCris.NumeroAgencia = 2654
contaDaCris.NumeroConta = 12456
contaDaCris.Saldo = 1000
fmt.Println(*contaDaCris)
// Realizando saques na conta da Cris
fmt.Println(contaDaCris.Sacar(500))
fmt.Println(*contaDaCris)
fmt.Println(contaDaCris.Sacar(1000))
fmt.Println(*contaDaCris)
fmt.Println(contaDaCris.Depositar(1000))
fmt.Println(*contaDaCris)
status, saldo := contaDaCris.Depositar(200)
fmt.Println(status)
fmt.Println(saldo)
// Criando a conta do Denisson
contaDoDenisson := new(contas.ContaCorrente)
contaDoDenisson.Titular = "Denisson Freitas"
contaDoDenisson.NumeroAgencia = 1222
contaDoDenisson.NumeroConta = 35694
contaDoDenisson.Saldo = 1200
fmt.Println(*contaDoDenisson)
// Testando transferências
fmt.Println("========== Valores iniciais nas contas dos usuários =========")
fmt.Println(*contaDoDenisson)
fmt.Println(*contaDaCris)
fmt.Println("========== Denisson transfere 200 para Cris =================")
contaDoDenisson.Transferir(200, contaDaCris)
fmt.Println("========== Valores nas contas após a transferência ==========")
fmt.Println(*contaDoDenisson)
fmt.Println(*contaDaCris)
fmt.Println("========== Cris transfere 400 para Denisson =================")
contaDaCris.Transferir(400, contaDoDenisson)
fmt.Println("========== Valores nas contas após a transferência ==========")
fmt.Println(*contaDoDenisson)
fmt.Println(*contaDaCris)
}
Explicação:
- Pacote
contas
: Define o tipoContaCorrente
com métodos para sacar, depositar e transferir valores. Os métodos são exportados porque começam com letras maiúsculas, permitindo que sejam usados em outros pacotes. - Pacote
main
: Importa o pacotecontas
e utiliza suas funções e tipos para criar e manipular instâncias deContaCorrente
. O código realiza operações de saque, depósito e transferência.
Pacotes são fundamentais para a organização e modularização de projetos Go, ajudando a separar responsabilidades e a manter o código mais limpo e gerenciável.
Composição em Go:
-
Conceito: Go não suporta herança tradicional como em outras linguagens orientadas a objetos. Em vez disso, Go usa a composição para promover a reutilização de código. A composição permite que uma estrutura (struct) seja construída a partir de outras estruturas, combinando suas funcionalidades de maneira modular.
-
Exemplo: No exemplo a seguir, a struct
ContaCorrente
inclui a structTitular
como um campo. Isso permite associar informações sobre o titular da conta diretamente à conta corrente, ilustrando a composição em ação.
Pacotes em Go:
-
Conceito: Pacotes são a unidade básica de organização de código em Go. Eles ajudam a agrupar funcionalidades relacionadas e a manter o código organizado e modular. Cada arquivo Go pertence a um pacote, e o nome do pacote é definido no início do arquivo com a palavra-chave
package
. -
Visibilidade: Em Go, a visibilidade de funções, métodos e variáveis é determinada pela convenção de nomenclatura: elementos que começam com uma letra maiúscula são exportados e acessíveis fora do pacote, enquanto aqueles que começam com uma letra minúscula são privados ao pacote.
-
Exemplo: O exemplo a seguir mostra a definição de dois pacotes,
clientes
econtas
, que contêm structs e métodos relacionados. O pacotemain
importa esses pacotes e utiliza suas funcionalidades para criar e manipular instâncias deContaCorrente
eTitular
.
Código de Exemplo:
-
Pacote
clientes
: Define a estruturaTitular
, que armazena informações sobre o cliente.package clientes type Titular struct { Nome string CPF string Profissao string }
-
Pacote
contas
: Define a estruturaContaCorrente
, que utilizaTitular
como um campo e implementa métodos para operar sobre a conta.package contas import ( "curso-go-poo/pkg/clientes" "fmt" ) type ContaCorrente struct { Titular *clientes.Titular NumeroAgencia int NumeroConta int saldo float64 } func (c *ContaCorrente) Sacar(valorDoSaque float64) string { podeSacar := valorDoSaque <= c.saldo && valorDoSaque > 0 if podeSacar { c.saldo -= valorDoSaque return "Saque realizado com sucesso!" } else { return "saldo insuficiente." } } func (c *ContaCorrente) Depositar(valorDoDeposito float64) (string, float64) { podeDepositar := valorDoDeposito > 0 if podeDepositar { c.saldo += valorDoDeposito return "Deposito realizado com sucesso!", c.saldo } else { return "Não foi possível realizar o deposito. Entre com um valor válido.", c.saldo } } func (c *ContaCorrente) Transferir(valorDaTransferencia float64, contaDestino *ContaCorrente) string { podeTransferir := valorDaTransferencia > 0 && c.saldo > valorDaTransferencia if podeTransferir { c.saldo -= valorDaTransferencia contaDestino.Depositar(valorDaTransferencia) return "Transferencia realizada com sucesso!" } else { return "Ocorreu um erro na transferência. Por gentileza, cheque se os campos inseridos estão corretos" } } func (c *ContaCorrente) ConsultarSaldo() string { return "Seu saldo é " + fmt.Sprintf("%.2f", c.saldo) }
-
Pacote
main
: Faz uso dos pacotesclientes
econtas
para criar e manipular instâncias deContaCorrente
eTitular
.package main import ( "curso-go-poo/pkg/clientes" "curso-go-poo/pkg/contas" "fmt" ) func main() { // Com Ponteiros fmt.Println("=========== Conta da Cris ===========") // Cliente Cris clienteCris := new(clientes.Titular) clienteCris.CPF = "063.580.380-10" clienteCris.Nome = "Cris Souza" clienteCris.Profissao = "Médica" // Conta da Cris contaDaCris := new(contas.ContaCorrente) contaDaCris.Titular = clienteCris contaDaCris.NumeroAgencia = 2654 contaDaCris.NumeroConta = 12456 contaDaCris.Depositar(12503.29) fmt.Println(*contaDaCris.Titular) fmt.Println(*contaDaCris) fmt.Println(contaDaCris.ConsultarSaldo()) fmt.Println("=========== Conta do Denisson ===========") // Cliente Denisson clienteDenisson := new(clientes.Titular) clienteDenisson.CPF = "538.240.490-90" clienteDenisson.Nome = "Denisson Freitas" clienteDenisson.Profissao = "Engenheiro DevOps" // Conta do Denisson contaDoDenisson := new(contas.ContaCorrente) contaDoDenisson.Titular = clienteDenisson contaDoDenisson.NumeroAgencia = 1222 contaDoDenisson.NumeroConta = 35694 contaDoDenisson.Depositar(43520.25) fmt.Println(*contaDoDenisson.Titular) fmt.Println(*contaDoDenisson) fmt.Println(contaDoDenisson.ConsultarSaldo()) }
Considerações:
- Composição: Go usa composição para criar tipos complexos de maneira modular, o que facilita a manutenção e a reutilização de código.
- Pacotes: Os pacotes ajudam a organizar o código de maneira lógica e modular, promovendo boas práticas de desenvolvimento.
Este exemplo demonstra como Go utiliza composição e pacotes para implementar conceitos de OOP de forma eficiente e adaptada à filosofia da linguagem.
Em Go, uma interface define um conjunto de métodos que uma struct deve implementar para ser considerada como implementadora dessa interface. Interfaces permitem que diferentes tipos de dados sejam tratados de maneira uniforme, desde que eles implementem os métodos da interface.
O projeto está organizado em diferentes pacotes e arquivos, conforme a estrutura abaixo:
E:.
│ go.mod
│ README.md
│
├───bin
├───pkg
│ ├───clientes
│ │ cliente.go
│ │
│ └───contas
│ conta.go
│ contaBase.go
│ contaCorrente.go
│ contaPoupanca.go
│
└───src
main.go
Arquivo conta.go
Define a interface Conta
, que inclui os métodos que todas as contas devem implementar:
package contas
type Conta interface {
Sacar(valor float64) string
Depositar(valor float64) (string, float64)
Transferir(valor float64, contaDestino Conta) string
ConsultarSaldo() string
}
Arquivo contaBase.go
Implementa uma struct ContaBase com métodos que definem o comportamento comum para todas as contas. Esta struct fornece uma implementação básica para os métodos definidos na interface Conta:
package contas
import (
"curso-go-poo/pkg/clientes"
"fmt"
)
type ContaBase struct {
Titular *clientes.Titular
NumeroAgencia int
NumeroConta int
saldo float64
}
func (c *ContaBase) Sacar(valorDoSaque float64) string {
podeSacar := valorDoSaque <= c.saldo && valorDoSaque > 0
if podeSacar {
c.saldo -= valorDoSaque
return "Saque realizado com sucesso!"
} else {
return "Saldo insuficiente."
}
}
func (c *ContaBase) Depositar(valorDoDeposito float64) (string, float64) {
podeDepositar := valorDoDeposito > 0
if podeDepositar {
c.saldo += valorDoDeposito
return "Depósito realizado com sucesso!", c.saldo
} else {
return "Não foi possível realizar o depósito. Entre com um valor válido.", c.saldo
}
}
func (c *ContaBase) Transferir(valorDaTransferencia float64, contaDestino Conta) string {
podeTransferir := valorDaTransferencia > 0 && c.saldo > valorDaTransferencia
if podeTransferir {
c.saldo -= valorDaTransferencia
contaDestino.Depositar(valorDaTransferencia)
return "Transferência realizada com sucesso!"
} else {
return "Ocorreu um erro na transferência. Verifique se os campos inseridos estão corretos."
}
}
func (c *ContaBase) ConsultarSaldo() string {
return "Seu saldo é " + fmt.Sprintf("%.2f", c.saldo)
}
func (c ContaBase) String() string {
return fmt.Sprintf("{%s %d %d %.2f}", c.Titular, c.NumeroAgencia, c.NumeroConta, c.saldo)
}
Arquivo contaCorrente.go
Define a struct ContaCorrente que embute ContaBase. Isso significa que ContaCorrente compõe todos os métodos e atributos de ContaBase:
package contas
type ContaCorrente struct {
ContaBase
}
Arquivo contaPoupanca.go
Define a struct ContaPoupanca, que também embute ContaBase e adiciona um campo extra Operacao
package contas
type ContaPoupanca struct {
ContaBase
Operacao int
}
Arquivo main.go
Demonstra o uso das structs e da interface. Cria instâncias de ContaCorrente e ContaPoupanca, e utiliza a interface Conta para realizar operações:
package main
import (
"curso-go-poo/pkg/clientes"
"curso-go-poo/pkg/contas"
"fmt"
)
func PagarBoleto(conta contas.Conta, valorDoBoleto float64) {
conta.Sacar(valorDoBoleto)
}
func main() {
fmt.Println("=========== Conta da Cris ===========")
// Cliente Cris
clienteCris := &clientes.Titular{
CPF: "063.580.380-10",
Nome: "Cris Souza",
Profissao: "Médica",
}
// Conta da Cris
contaDaCris := &contas.ContaCorrente{
ContaBase: contas.ContaBase{
Titular: clienteCris,
NumeroAgencia: 2654,
NumeroConta: 12456,
},
}
contaDaCris.Depositar(12503.29)
fmt.Println(*contaDaCris.Titular)
fmt.Println(*contaDaCris)
fmt.Println(contaDaCris.ConsultarSaldo())
fmt.Println("=========== Conta do Denisson ===========")
// Cliente Denisson
clienteDenisson := &clientes.Titular{
CPF: "538.240.490-90",
Nome: "Denisson Freitas",
Profissao: "Engenheiro DevOps",
}
// Conta do Denisson
contaDoDenisson := &contas.ContaPoupanca{
ContaBase: contas.ContaBase{
Titular: clienteDenisson,
NumeroAgencia: 1222,
NumeroConta: 35694,
},
Operacao: 5,
}
contaDoDenisson.Depositar(43520.25)
fmt.Println(*contaDoDenisson.Titular)
fmt.Println(*contaDoDenisson)
fmt.Println(contaDoDenisson.ConsultarSaldo())
fmt.Println("=========== Conta do Denisson : Pagar Boleto ===========")
PagarBoleto(contaDoDenisson, 520.25)
fmt.Println(contaDoDenisson.ConsultarSaldo())
}
-
Interface Conta: Define um contrato que as structs ContaCorrente e ContaPoupanca devem seguir, permitindo uma manipulação uniforme de diferentes tipos de contas.
-
Struct ContaBase: Fornece a implementação comum para os métodos da interface Conta, evitando duplicação de código.
-
Structs ContaCorrente e ContaPoupanca: Estendem ContaBase, aproveitando a composição para adicionar ou modificar comportamentos específicos sem redefinir os métodos básicos.
-
Uso da Interface: No main.go, a função PagarBoleto usa a interface Conta para realizar operações de saque, demonstrando como a interface permite a manipulação de diferentes tipos de contas de forma abstrata e uniforme.
Este exemplo mostra como utilizar interfaces para promover a flexibilidade e a modularidade em Go, facilitando a extensão e manutenção do código.