Trabahlando com Interfaces
Em Go, uma interface é definida como um tipo abstrato que descreve um conjunto de assinaturas de métodos. Diferente de outras linguagens, a implementação de uma interface é implícita: se um tipo possui todos os métodos declarados na interface, ele a implementa automaticamente, sem a necessidade de palavras-chave como implements.
type Notificador interface {
Enviar(mensagem string) error
Status() string
}
Ao lidar com interfaces, é importante observar que:
- Interfaces podem ser compostas (nested) por outras interfaces.
- Se os métodos de uma interface utilizam receptores de ponteiro (pointer receivers), apenas ponteiros para o tipo podem satisfazer a interface.
- A
interface{}vazia (ouanynas versões recentes) aceita qualquer valor, sendo útil para funções genéricas.
package main
import "fmt"
type Repositorio interface {
Salvar(dados string)
}
type BancoDeDados struct {
Conectado bool
}
func (db *BancoDeDados) Salvar(dados string) {
if db.Conectado {
fmt.Printf("Dados salvos no SQL: %s\n", dados)
}
}
type CacheMemoria struct {
Capacidade int
}
func (c CacheMemoria) Salvar(dados string) {
fmt.Printf("Dados armazenados em cache: %s\n", dados)
}
func ProcessarEntrada(r Repositorio, info string) {
r.Salvar(info)
}
func main() {
sql := &BancoDeDados{Conectado: true}
mem := CacheMemoria{Capacidade: 1024}
ProcessarEntrada(sql, "Log de sistema")
ProcessarEntrada(mem, "Temporário")
}
Concorrência: Goroutines e Channels
O modelo de concorrência de Go é baseado em Goroutines e Channels. Uma goroutine é uma unidade de execução extremamente leve gerenciada pelo Go runtime, e não diretamente pelo sistema operacional, o que permite a criação de milhares delas com baixo custo de memória.
// Exemplo de invocação
go realizarTarefa(parametro)
// Invocação via função anônima
go func(x int) {
fmt.Println(x * x)
}(10)
Primitivas de Sincronização (Pacote sync)
Embora a comuincação via channels seja preferível, Go oferece o pacote sync para cenários que exigem controle de acesso à memória compartilhada ou sincronização direta entre goroutines.
sync.Mutex e sync.RWMutex
O Mutex garante que apenas uma goroutine acesse uma região crítica de cada vez. O RWMutex otimiza esse processo permitindo que múltiplos leitores acessem o recurso simultaneamente, bloqueando apenas quando uma operação de escrita é necessária.
type Contador struct {
mu sync.RWMutex
total int
}
func (c *Contador) Incrementar() {
c.mu.Lock()
defer c.mu.Unlock()
c.total++
}
func (c *Contador) Valor() int {
c.mu.RLock()
defer c.mu.RUnlock()
return c.total
}
sync.WaitGroup
Utilizado para aguardar a finalização de um grupo de goroutines. Funicona como um contador: Add incrementa, Done decrementa e Wait bloqueia a execução até que o contador chegue a zero.
func dispararTrabalhadores() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("Trabalhador %d finalizou\n", id)
}(i)
}
wg.Wait()
}
sync.Once e sync.Cond
O sync.Once garante que uma função seja executada exatamente uma vez, ideal para inicialização de singletons. Já o sync.Cond é um mecanismo para sinalizar eventos entre goroutines que estão aguardando uma condição específica.
func exemploCond() {
trava := &sync.Mutex{}
condicao := sync.NewCond(trava)
go func() {
trava.Lock()
fmt.Println("Aguardando sinalização...")
condicao.Wait()
fmt.Println("Sinal recebido!")
trava.Unlock()
}()
time.Sleep(time.Second)
condicao.Signal() // Acorda uma goroutine
}
Comunicação com Channels e Select
Channels são condutos por onde dados fluem entre goroutines de forma segura. O operador select permite que uma goroutine monitore múltiplos channels simultaneamente, executando o primeiro caso que estiver pronto.
func multiplexador(c1, c2 chan string) {
for i := 0; i < 2; i++ {
select {
case msg1 := <-c1:
fmt.Println("Recebido de C1:", msg1)
case msg2 := <-c2:
fmt.Println("Recebido de C2:", msg2)
case <-time.After(time.Second * 3):
fmt.Println("Timeout atingido")
}
}
}
Processo de Compilação e Build Tags
Go oferece um sistema robusto para gerar executáveis em diferentes plataformas através de compilação cruzada e condicional.
Compilação Cruzada
Ao definir as variáveis de ambiente GOOS e GOARCH, é possível compilar binários para sistemas operacionais e arquiteturas diferentes da máquina local:
# Compilar para Linux 64-bit
GOOS=linux GOARCH=amd64 go build -o app_linux main.go
# Compilar para Windows 64-bit
GOOS=windows GOARCH=amd64 go build -o app.exe main.go
Compilação Condicional (Build Tags)
As build tags permitem incluir ou excluir arquivos do processo de compilação com base em critérios como SO, versão do compilador ou tags customizadas. Na sintaxe moderna (Go 1.17+), utiliza-se a diretiva //go:build.
// Arquivo: log_debug.go
//go:build debug
package main
import "fmt"
func Log(v string) {
fmt.Println("[DEBUG]:", v)
}
Para compilar incluindo o arquivo acima, utiliza-se a flag de tags no comando build:
go build -tags="debug" -o app_debug
As regras de lógica para tags incluem:
- Espaço: Representa a operação lógica OR.
- Vírgula (ou múltiplas linhas): Representa a operação lógica AND.
- Exclamação (!): Representa a negação (NOT).