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.
Desafio
FácilVamos 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 interfaceNotifiercom um único métodoNotify(message string) string. Também crie uma structBaseNotifiercom um campoNameque será incorporado por outros tipos. Dê aoBaseNotifierum método chamadoFormat(message string) stringque retorna a mensagem prefixada com o nome do notificador entre colchetes.channels.go: Crie dois tipos de canais de notificação que incorporamBaseNotifiere implementam a interfaceNotifier:EmailNotifiercom um campo adicionalAddressSMSNotifiercom um campo adicionalPhone
Notifyde cada tipo deve usar o métodoFormatincorporado e incluir suas informações específicas de canal na saída.main.go: Crie uma função chamadaSendAlertque aceita qualquerNotifiere uma mensagem, e então retorna o resultado da chamada deNotify. 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] messageSeus 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 downObserve 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
}
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
1Fundamentos de OOP em Go
Arquivos ExternosGo Workspace e MódulosPacotes e ImportaçõesNomes Exportados vs Não ExportadosIntrodução a OOP em GoStructs como ClassesDefinindo Métodos em StructsRecebedores de Ponteiro vs ValorInicialização de StructsFunções ConstrutorasRecapitulação - Calculadora Simples4Interfaces
Introdução a InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Type AssertionType SwitchComposição de InterfacesInterfaces Stringer & ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs Não ExportadosEncapsulamento em Nível de PacoteMétodos Getter e SetterOcultação de Informação em GoRecapitulação - Registros de Estudantes10Generics (Go 1.18+)
Introdução a GenericsParâmetros de TipoRestrições de TipoStructs GenéricasSolução Alternativa para Métodos GenéricosRecapitulação - Coleção Genérica2Mergulho Profundo em Tipos e Structs
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosStruct TagsStructs AnônimasStructs AninhadasZero Values e PadrõesRecapitulação - Agenda de Contatos5Composição em vez de Herança
Por que Go não possui HerançaFundamentos de Incorporação de StructsPromoção de MétodosIncorporando Múltiplas StructsIncorporação vs AgregaçãoShadowing de Métodos IncorporadosRecapitulação - Hierarquia de Funcionários8Tratamento de Erros e OOP
A Interface errorTipos de Erro PersonalizadosError Wrapping (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - File Parser3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs ReferênciaA Função new()Garbage Collection em GoRecapitulação - Construtor de Lista Encadeada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfaceColeções PolimórficasInjeção de DependênciaRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e ComunicaçãoCanais com Buffer vs. Sem BufferInstrução Selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de Structs Thread-SafeRecapitulação - Worker Pool