Menu
Coddy logo textTech

Por que Go não possui Herança

Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 34 de 107.

Linguagens orientadas a objetos tradicionais, como Java e C++, usam herança para compartilhar código entre tipos. Uma classe filha estende uma classe pai, herdando todos os seus campos e métodos. Go omite deliberadamente esse recurso.

A herança cria um acoplamento forte entre os tipos. Quando uma classe pai muda, todas as classes filhas são afetadas.

Hierarquias de herança profundas tornam-se difíceis de entender e manter. O "problema da classe base frágil" ocorre quando modificações em uma classe base quebram inesperadamente as classes derivadas.

Os designers do Go escolheram um caminho diferente: composição em vez de herança. Em vez de dizer "um Cachorro é um Animal", o Go incentiva você a dizer "um Cachorro tem comportamentos semelhantes aos de um Animal". Essa mudança sutil leva a um código mais flexível e fácil de manter.

O Go alcança o reuso de código por meio de dois mecanismos que você já aprendeu:

  • Interfaces definem contratos de comportamento sem detalhes de implementação
  • Incorporação de structs permite que tipos incluam outros tipos e reutilizem seus métodos

Considere esta comparação. Na POO tradicional, você pode escrever class Dog extends Animal. Em Go, você incorpora uma struct Animal dentro de Dog e implementa interfaces compartilhadas. O resultado é uma funcionalidade semelhante com um acoplamento mais fraco entre os tipos.

Este capítulo explora a incorporação de structs em profundidade, mostrando como o Go alcança os benefícios da herança sem suas desvantagens.

challenge icon

Desafio

Fácil

Vamos construir um sistema de notificação que demonstra a abordagem de composição do Go em vez de herança. Você criará tipos que compartilham comportamento por meio de interfaces e incorporação de structs (struct embedding) em vez de hierarquias de classes.

Você organizará seu código em três arquivos:

  • notifier.go: Defina uma interface Notifier com um único método Notify(message string) string. Também crie uma struct BaseNotifier com um campo Name que será incorporado por outros tipos. Dê ao BaseNotifier um método chamado Format(message string) string que retorna a mensagem prefixada com o nome do notificador entre colchetes.
  • channels.go: Crie dois tipos de canais de notificação que incorporam BaseNotifier e implementam a interface Notifier:
    • EmailNotifier com um campo adicional Address
    • SMSNotifier com um campo adicional Phone
    O método Notify de cada tipo deve usar o método Format incorporado e incluir suas informações específicas de canal na saída.
  • main.go: Crie uma função chamada SendAlert que aceita qualquer Notifier e uma mensagem, e então retorna o resultado da chamada de Notify. Leia os detalhes da notificação da entrada, crie ambos os tipos de notificadores e demonstre como eles podem ser usados de forma intercambiável através da interface.

As seguintes entradas serão fornecidas:

  • Linha 1: Nome do notificador para e-mail
  • Linha 2: Endereço de e-mail
  • Linha 3: Nome do notificador para SMS
  • Linha 4: Número de telefone
  • Linha 5: Mensagem de alerta

Seu método Format no BaseNotifier deve retornar:

[Name] message

Seus métodos Notify devem retornar:

  • EmailNotifier: Email to [Address]: [formatted message]
  • SMSNotifier: SMS to [Phone]: [formatted message]

Por exemplo, dados Alerts, user@mail.com, Urgent, 555-1234, e Server down, sua saída deve ser:

Email to user@mail.com: [Alerts] Server down
SMS to 555-1234: [Urgent] Server down

Observe como ambos os tipos de notificador reutilizam o método Format do BaseNotifier através da incorporação, enquanto cada um fornece sua própria implementação de Notify. A função SendAlert funciona com qualquer Notifier sem conhecer o tipo concreto — isso é composição sobre herança em ação.

Folha de consulta

Go utiliza composição em vez de herança para compartilhar código entre tipos, evitando o acoplamento forte e os problemas de classe base frágil da herança orientada a objetos tradicional.

Em vez de hierarquias de classes, Go alcança a reutilização de código por meio de:

  • Interfaces - definem contratos de comportamento sem detalhes de implementação
  • Struct embedding - permite que tipos incluam outros tipos e reutilizem seus métodos

Em vez de dizer "um Cachorro é um Animal" (herança), Go incentiva "um Cachorro tem comportamentos semelhantes aos de um Animal" (composição).

Exemplo de Struct Embedding

Defina uma struct base que será incorporada:

type BaseNotifier struct {
    Name string
}

func (b BaseNotifier) Format(message string) string {
    return "[" + b.Name + "] " + message
}

Incorpore a struct base em outros tipos para reutilizar seus campos e métodos:

type EmailNotifier struct {
    BaseNotifier  // struct incorporada
    Address string
}

func (e EmailNotifier) Notify(message string) string {
    // Pode chamar o método Format incorporado diretamente
    return "Email to " + e.Address + ": " + e.Format(message)
}

Defina uma interface que múltiplos tipos podem implementar:

type Notifier interface {
    Notify(message string) string
}

Use a interface para trabalhar com diferentes tipos concretos de forma intercambiável:

func SendAlert(n Notifier, message string) string {
    return n.Notify(message)
}

Essa abordagem fornece funcionalidade semelhante à herança, mas com um acoplamento mais fraco entre os tipos.

Experimente você mesmo

package main

import (
	"bufio"
	"fmt"
	"os"
)

// SendAlert aceita qualquer Notifier e uma mensagem, retorna o resultado da chamada de Notify
// TODO: Implementar a função SendAlert

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// Ler o nome do notificador de e-mail
	scanner.Scan()
	emailName := scanner.Text()
	
	// Ler o endereço de e-mail
	scanner.Scan()
	emailAddress := scanner.Text()
	
	// Ler o nome do notificador de SMS
	scanner.Scan()
	smsName := scanner.Text()
	
	// Ler o número de telefone
	scanner.Scan()
	phoneNumber := scanner.Text()
	
	// Ler a mensagem de alerta
	scanner.Scan()
	alertMessage := scanner.Text()
	
	// TODO: Criar um EmailNotifier com emailName e emailAddress
	
	// TODO: Criar um SMSNotifier com smsName e phoneNumber
	
	// TODO: Usar SendAlert para enviar a alertMessage através de ambos os notificadores
	// e imprimir os resultados
	
	// Suprimir avisos de variáveis não utilizadas (remova-os quando usar as variáveis)
	_ = emailName
	_ = emailAddress
	_ = smsName
	_ = phoneNumber
	_ = alertMessage
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos